monocart-coverage-reports 2.8.5 → 2.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/generate.js CHANGED
@@ -21,7 +21,6 @@ const { getGroupedRows } = require('./utils/snapshot.js');
21
21
 
22
22
  // maybe v8 or to istanbul reports
23
23
  const generateV8ListReports = async (v8list, coverageData, fileSources, options) => {
24
- const time_start = Date.now();
25
24
  let istanbulReportPath;
26
25
  // v8 to istanbul reports
27
26
  if (options.reportGroup.istanbul) {
@@ -32,7 +31,6 @@ const generateV8ListReports = async (v8list, coverageData, fileSources, options)
32
31
  // v8 reports and v8 coverage results
33
32
  // could be no v8 or v8-json, but requires v8 coverage results
34
33
  const v8CoverageResults = await saveV8Report(v8list, options, istanbulReportPath);
35
- Util.logTime('- saved coverage reports', time_start);
36
34
  return v8CoverageResults;
37
35
  };
38
36
 
@@ -92,6 +90,7 @@ const getReportGroup = (reports, lcov, dataType) => {
92
90
 
93
91
  // both
94
92
  'codecov': 'both',
93
+ 'codacy': 'both',
95
94
  'console-details': 'both',
96
95
  'console-summary': 'both',
97
96
  'raw': 'both'
@@ -282,6 +281,42 @@ const handleCodecovReport = async (reportData, reportOptions, options) => {
282
281
  return Util.relativePath(jsonPath);
283
282
  };
284
283
 
284
+ const handleCodacyReport = async (reportData, reportOptions, options) => {
285
+ const codacyOptions = {
286
+ outputFile: 'codacy.json',
287
+ ... reportOptions
288
+ };
289
+
290
+ const jsonPath = path.resolve(options.outputDir, codacyOptions.outputFile);
291
+
292
+ // https://api.codacy.com/swagger#tocscoveragereport
293
+ const fileReports = [];
294
+ reportData.files.forEach((item) => {
295
+ const { sourcePath, data } = item;
296
+
297
+ const coverage = {};
298
+ for (const [key, value] of Object.entries(data.lines)) {
299
+ if (typeof value === 'number') {
300
+ coverage[key] = value;
301
+ } else {
302
+ // partial coverage not supported?
303
+ coverage[key] = 0;
304
+ }
305
+ }
306
+
307
+ fileReports.push({
308
+ filename: sourcePath,
309
+ coverage
310
+ });
311
+ });
312
+ const codacyData = {
313
+ fileReports
314
+ };
315
+
316
+ await Util.writeFile(jsonPath, JSON.stringify(codacyData));
317
+ return Util.relativePath(jsonPath);
318
+ };
319
+
285
320
  const handleConsoleDetailsReport = (reportData, reportOptions, options) => {
286
321
  const cdOptions = {
287
322
  maxCols: 50,
@@ -434,9 +469,15 @@ const getCoverageResults = async (dataList, sourceCache, options) => {
434
469
  // v8list
435
470
  if (dataType === 'v8') {
436
471
  // merge v8list first
472
+ const t1 = Date.now();
437
473
  const v8list = await mergeV8Coverage(dataList, sourceCache, options);
474
+ Util.logTime('┌ [generate] merged v8 coverage data', t1);
438
475
  // console.log('after merge', v8list.map((it) => it.url));
476
+
477
+ const t2 = Date.now();
439
478
  const results = convertV8List(v8list, options);
479
+ Util.logTime('┌ [generate] converted coverage data', t2);
480
+
440
481
  const {
441
482
  v8DataList, coverageData, fileSources
442
483
  } = results;
@@ -444,9 +485,12 @@ const getCoverageResults = async (dataList, sourceCache, options) => {
444
485
  }
445
486
 
446
487
  // istanbul data
488
+ const t3 = Date.now();
447
489
  const istanbulData = mergeIstanbulCoverage(dataList);
448
490
  const { coverageData, fileSources } = initIstanbulData(istanbulData, options);
449
- return saveIstanbulReports(coverageData, fileSources, options);
491
+ Util.logTime('┌ [generate] prepared istanbul coverage data', t3);
492
+ const results = await saveIstanbulReports(coverageData, fileSources, options);
493
+ return results;
450
494
  };
451
495
 
452
496
  const generateCoverageReports = async (dataList, sourceCache, options) => {
@@ -457,6 +501,7 @@ const generateCoverageReports = async (dataList, sourceCache, options) => {
457
501
 
458
502
  const buildInBothReports = {
459
503
  'codecov': handleCodecovReport,
504
+ 'codacy': handleCodacyReport,
460
505
  'console-details': handleConsoleDetailsReport,
461
506
  'console-summary': handleConsoleSummaryReport,
462
507
  'raw': handleRawReport
@@ -468,11 +513,13 @@ const generateCoverageReports = async (dataList, sourceCache, options) => {
468
513
  for (const reportName of bothReports) {
469
514
  const reportOptions = bothGroup[reportName];
470
515
  const buildInHandler = buildInBothReports[reportName];
516
+ const t1 = Date.now();
471
517
  if (buildInHandler) {
472
518
  await buildInHandler(coverageResults, reportOptions, options);
473
519
  } else {
474
520
  await Util.runCustomReporter(reportName, coverageResults, reportOptions, options);
475
521
  }
522
+ Util.logTime(`┌ [generate] saved report: ${reportName}`, t1);
476
523
  }
477
524
  }
478
525
 
@@ -663,28 +710,111 @@ const getEmptyData = (all, dataList) => {
663
710
 
664
711
  // ========================================================================================================
665
712
 
713
+ const getInputList = (mcr) => {
714
+ // get input dirs
715
+ const { inputDir, cacheDir } = mcr.options;
666
716
 
667
- const getInputData = async (inputList, all, cacheDir, addHandler) => {
668
- const dataList = [];
669
- const sourceCache = new Map();
717
+ const inputDirs = Util.toList(inputDir, ',');
718
+
719
+ const inputList = inputDirs.filter((dir) => {
720
+ const hasDir = fs.existsSync(dir);
721
+ if (!hasDir) {
722
+ // could be empty
723
+ Util.logInfo(`Not found coverage data dir: ${Util.relativePath(dir)}`);
724
+ }
725
+ return hasDir;
726
+ });
727
+
728
+ if (mcr.hasCache()) {
729
+ inputList.push(cacheDir);
730
+ }
731
+
732
+ return inputList;
733
+ };
670
734
 
671
- const addJsonData = async (dir, filename) => {
672
- const isCoverage = filename.startsWith('coverage-');
673
- const isSource = filename.startsWith('source-');
674
- if (isCoverage || isSource) {
735
+ const addJsonData = async (mcr, dataList, sourceCache, dir, filename) => {
736
+ const isCoverage = filename.startsWith('coverage-');
737
+ const isSource = filename.startsWith('source-');
738
+ if (isCoverage || isSource) {
739
+ let json;
740
+ if (mcr.fileCache.has(filename)) {
741
+ json = mcr.fileCache.get(filename);
742
+ } else {
675
743
  const content = await Util.readFile(path.resolve(dir, filename));
676
744
  if (content) {
677
- const json = JSON.parse(content);
678
- if (isCoverage) {
679
- dataList.push(json);
680
- } else {
745
+ json = JSON.parse(content);
746
+ }
747
+ }
748
+ if (json) {
749
+ if (isCoverage) {
750
+ dataList.push(json);
751
+ } else {
752
+ sourceCache.set(json.id, json);
753
+ }
754
+ }
755
+ }
756
+ };
757
+
758
+ const addEmptyData = async (mcr, dataList, sourceCache) => {
759
+
760
+ const { all, cacheDir } = mcr.options;
761
+ // add empty coverage for all files
762
+ // dataList for data type, before `add` we don't which one is tested, because using hash id
763
+ const emptyData = getEmptyData(all, dataList);
764
+ if (!emptyData) {
765
+ return;
766
+ }
767
+
768
+ let files = 0;
769
+ if (Util.isList(emptyData)) {
770
+ files = emptyData.length;
771
+ } else {
772
+ files = Object.keys(emptyData).length;
773
+ }
774
+
775
+ Util.logDebug(`added all files with empty coverage: ${EC.yellow(files)}`);
776
+ const coverageResults = await mcr.add(emptyData);
777
+ // after `add`
778
+ if (!coverageResults) {
779
+ return;
780
+ }
781
+
782
+ dataList.push(coverageResults);
783
+
784
+ if (coverageResults.type === 'v8') {
785
+ // sourceCache
786
+ coverageResults.data.forEach((entry) => {
787
+ const { id } = entry;
788
+ if (sourceCache.has(id)) {
789
+ // console.log('+', entry.sourcePath);
790
+ return;
791
+ }
792
+ // console.log('-', entry.sourcePath);
793
+ const { cacheName, cachePath } = Util.getCacheFileInfo('source', id, cacheDir);
794
+ if (mcr.fileCache.has(cacheName)) {
795
+ const json = mcr.fileCache.get(cacheName);
796
+ sourceCache.set(json.id, json);
797
+ } else {
798
+ const content = Util.readFileSync(cachePath);
799
+ if (content) {
800
+ const json = JSON.parse(content);
681
801
  sourceCache.set(json.id, json);
682
802
  }
683
803
  }
684
- }
685
- };
804
+ });
805
+ }
806
+ };
807
+
808
+ const getInputData = async (mcr) => {
809
+
810
+ const inputList = getInputList(mcr);
811
+ // console.log('input list', inputList);
812
+
813
+ const dataList = [];
814
+ const sourceCache = new Map();
686
815
 
687
816
  for (const dir of inputList) {
817
+
688
818
  const allFiles = fs.readdirSync(dir);
689
819
  if (!allFiles.length) {
690
820
  continue;
@@ -692,38 +822,17 @@ const getInputData = async (inputList, all, cacheDir, addHandler) => {
692
822
  for (const filename of allFiles) {
693
823
  // only json file
694
824
  if (filename.endsWith('.json')) {
695
- await addJsonData(dir, filename);
825
+ await addJsonData(mcr, dataList, sourceCache, dir, filename);
696
826
  }
697
827
  }
698
828
  }
699
829
 
700
- // add empty coverage for all files
701
- // dataList for data type, before `add` we don't which one is tested, because using hash id
702
- const emptyData = getEmptyData(all, dataList);
703
- if (emptyData) {
704
- Util.logDebug(`added empty files for all: ${EC.yellow(emptyData.length)}`);
705
- const coverageResults = await addHandler(emptyData);
706
- // after `add`
707
- if (coverageResults) {
708
- dataList.push(coverageResults);
709
- if (coverageResults.type === 'v8') {
710
- // sourceCache
711
- coverageResults.data.forEach((entry) => {
712
- const { id } = entry;
713
- if (sourceCache.has(id)) {
714
- // console.log('+', entry.sourcePath);
715
- return;
716
- }
717
- // console.log('-', entry.sourcePath);
718
- const sourcePath = Util.resolveCacheSourcePath(cacheDir, id);
719
- const content = Util.readFileSync(sourcePath);
720
- if (content) {
721
- const json = JSON.parse(content);
722
- sourceCache.set(json.id, json);
723
- }
724
- });
725
- }
726
- }
830
+ await addEmptyData(mcr, dataList, sourceCache);
831
+
832
+ if (!dataList.length) {
833
+ const dirs = inputList.map((dir) => Util.relativePath(dir));
834
+ Util.logError(`Not found coverage data in dir(s): ${dirs.join(', ')}`);
835
+ return;
727
836
  }
728
837
 
729
838
  return {
@@ -783,14 +892,18 @@ const resolveEntrySourceMap = (entry, sourceMapCache) => {
783
892
  }
784
893
  };
785
894
 
786
- const readCoverageData = (filePath, entryFilter, sourceCache) => {
895
+ const readCoverageData = async (dir, filename, entryFilter, sourceCache) => {
787
896
 
788
- const content = fs.readFileSync(filePath).toString('utf-8');
897
+ const content = await Util.readFile(path.resolve(dir, filename));
789
898
  if (!content) {
790
899
  return;
791
900
  }
792
-
793
901
  const json = JSON.parse(content);
902
+ if (!json) {
903
+ return;
904
+ }
905
+
906
+ // raw v8 json
794
907
  let coverageData = json.result;
795
908
  if (!Util.isList(coverageData)) {
796
909
  return;
@@ -818,7 +931,27 @@ const readCoverageData = (filePath, entryFilter, sourceCache) => {
818
931
  return coverageData;
819
932
  };
820
933
 
821
- const readFromDir = async (dir, entryFilter, addHandler) => {
934
+ const readSourceList = async (dir, sourceList) => {
935
+ const sourceCache = {};
936
+
937
+ for (const filename of sourceList) {
938
+ const content = await Util.readFile(path.resolve(dir, filename));
939
+ if (!content) {
940
+ continue;
941
+ }
942
+ const json = JSON.parse(content);
943
+ if (!json) {
944
+ continue;
945
+ }
946
+ if (json.url) {
947
+ sourceCache[json.url] = json;
948
+ }
949
+ }
950
+
951
+ return sourceCache;
952
+ };
953
+
954
+ const readFromDir = async (mcr, dir) => {
822
955
 
823
956
  if (!dir || !fs.existsSync(dir)) {
824
957
  Util.logInfo(`Not found V8 coverage dir: ${dir}`);
@@ -826,41 +959,41 @@ const readFromDir = async (dir, entryFilter, addHandler) => {
826
959
  }
827
960
 
828
961
  const files = fs.readdirSync(dir);
829
- if (!files.length) {
830
- Util.logInfo(`No coverage files in the dir: ${dir}`);
831
- return;
832
- }
833
962
 
834
- const coverageFiles = [];
835
- const sourceCache = {};
963
+ const coverageList = [];
964
+ const sourceList = [];
965
+
836
966
  files.forEach((filename) => {
837
- const isCoverage = filename.startsWith('coverage-');
838
- if (isCoverage) {
839
- coverageFiles.push(filename);
840
- return;
841
- }
842
- const isSource = filename.startsWith('source-');
843
- if (isSource) {
844
- const filePath = path.resolve(dir, filename);
845
- const content = fs.readFileSync(filePath).toString('utf-8');
846
- if (content) {
847
- const json = JSON.parse(content);
848
- if (json.url) {
849
- sourceCache[json.url] = json;
850
- }
967
+ // read all json files
968
+ if (filename.endsWith('.json')) {
969
+ // could be source files generated by register hooks
970
+ if (filename.startsWith('source-')) {
971
+ sourceList.push(filename);
972
+ } else {
973
+ coverageList.push(filename);
851
974
  }
852
975
  }
853
976
  });
854
977
 
855
- // filter before adding
856
- for (const filename of coverageFiles) {
857
- const filePath = path.resolve(dir, filename);
858
- const coverageData = readCoverageData(filePath, entryFilter, sourceCache);
978
+ if (!coverageList.length) {
979
+ Util.logInfo(`No coverage files in the dir: ${dir}`);
980
+ return;
981
+ }
982
+
983
+ const sourceCache = await readSourceList(dir, sourceList);
984
+
985
+ const entryFilter = mcr.getEntryFilter();
986
+
987
+ const results = [];
988
+ for (const filename of coverageList) {
989
+ const coverageData = await readCoverageData(dir, filename, entryFilter, sourceCache);
859
990
  if (coverageData) {
860
- await addHandler(coverageData);
991
+ const res = await mcr.add(coverageData);
992
+ results.push(res);
861
993
  }
862
994
  }
863
995
 
996
+ return results;
864
997
  };
865
998
 
866
999
  module.exports = {
package/lib/index.d.ts CHANGED
@@ -107,6 +107,9 @@ declare namespace MCR {
107
107
  ['codecov'] | ["codecov", {
108
108
  outputFile?: string;
109
109
  }] |
110
+ ['codacy'] | ["codacy", {
111
+ outputFile?: string;
112
+ }] |
110
113
  ['console-summary'] | ['console-summary', {
111
114
  metrics?: Array<"bytes" | "statements" | "branches" | "functions" | "lines">;
112
115
  }] |
@@ -190,7 +193,7 @@ declare namespace MCR {
190
193
  branches?: CoverageRange[];
191
194
  functions?: CoverageRange[];
192
195
  ignores?: IgnoredRange[];
193
- /** codecov json format */
196
+ /** common json format */
194
197
  lines: {
195
198
  [key: string]: number | string;
196
199
  };
package/lib/index.js CHANGED
@@ -53,6 +53,7 @@ class CoverageReport {
53
53
  ... options
54
54
  };
55
55
  this.initOptions();
56
+ this.fileCache = new Map();
56
57
  }
57
58
 
58
59
  initOptions(force) {
@@ -97,9 +98,6 @@ class CoverageReport {
97
98
  }
98
99
 
99
100
  const dataId = Util.uid();
100
- const dataName = `coverage-${dataId}.json`;
101
- const dataPath = path.resolve(this.options.cacheDir, dataName);
102
-
103
101
  const results = {
104
102
  id: dataId
105
103
  };
@@ -107,15 +105,16 @@ class CoverageReport {
107
105
  if (Array.isArray(data)) {
108
106
  results.type = 'v8';
109
107
  // could be empty list after entryFilter
110
- results.data = await initV8ListAndSourcemap(data, this.options);
108
+ results.data = await initV8ListAndSourcemap(this, data);
111
109
  } else {
112
110
  results.type = 'istanbul';
113
111
  results.data = data;
114
112
  }
115
113
 
116
- // save data to cache dir
117
- const dataContent = JSON.stringify(results);
118
- await Util.writeFile(dataPath, dataContent);
114
+ // save data to cache
115
+ const { cacheName, cachePath } = Util.getCacheFileInfo('coverage', dataId, this.options.cacheDir);
116
+ this.fileCache.set(cacheName, results);
117
+ await Util.writeFile(cachePath, JSON.stringify(results));
119
118
 
120
119
  Util.logTime(`added coverage data: ${results.type}`, time_start);
121
120
 
@@ -123,11 +122,11 @@ class CoverageReport {
123
122
  }
124
123
 
125
124
  // add coverage from dir
126
- addFromDir(dir) {
127
- const entryFilter = this.getEntryFilter();
128
- return readFromDir(dir, entryFilter, (coverageData) => {
129
- return this.add(coverageData);
130
- });
125
+ async addFromDir(dir) {
126
+ const time_start = Date.now();
127
+ const results = await readFromDir(this, dir);
128
+ Util.logTime(`added from dir: ${dir}`, time_start);
129
+ return results;
131
130
  }
132
131
 
133
132
  // generate report
@@ -137,33 +136,15 @@ class CoverageReport {
137
136
 
138
137
  this.initOptions();
139
138
 
140
- const {
141
- all, inputDir, cacheDir
142
- } = this.options;
143
-
144
- const inputDirs = Util.toList(inputDir, ',');
145
- if (this.hasCache()) {
146
- inputDirs.push(cacheDir);
139
+ const inputData = await getInputData(this);
140
+ if (!inputData) {
141
+ return;
147
142
  }
148
143
 
149
- const inputList = inputDirs.filter((dir) => {
150
- const hasDir = fs.existsSync(dir);
151
- if (!hasDir) {
152
- // could be empty
153
- Util.logInfo(`Not found coverage data dir: ${Util.relativePath(dir)}`);
154
- }
155
- return hasDir;
156
- });
157
-
158
- const { dataList, sourceCache } = await getInputData(inputList, all, cacheDir, (emptyData) => {
159
- return this.add(emptyData);
160
- });
144
+ // GC, no OOM
145
+ this.fileCache.clear();
161
146
 
162
- if (!dataList.length) {
163
- const dirs = inputList.map((dir) => Util.relativePath(dir));
164
- Util.logError(`Not found coverage data in dir(s): ${dirs.join(', ')}`);
165
- return;
166
- }
147
+ const { dataList, sourceCache } = inputData;
167
148
 
168
149
  // empty output dir except cache dir before generate reports
169
150
  const outputDir = this.options.outputDir;
@@ -179,19 +160,26 @@ class CoverageReport {
179
160
  });
180
161
  }
181
162
 
182
- const coverageResults = await generateCoverageReports(dataList, sourceCache, this.options);
183
-
184
- if (this.logging !== 'debug') {
185
- Util.rmSync(cacheDir);
186
- }
163
+ Util.logTime('┌ [generate] prepared coverage data', time_start);
187
164
 
188
- Util.logTime(`generated coverage reports: ${EC.cyan(coverageResults.reportPath)}`, time_start);
165
+ const coverageResults = await generateCoverageReports(dataList, sourceCache, this.options);
189
166
 
190
167
  const onEnd = this.options.onEnd;
191
168
  if (typeof onEnd === 'function') {
192
169
  await onEnd.call(this, coverageResults);
193
170
  }
194
171
 
172
+ // remove cache dir after finished
173
+ if (!Util.isDebug()) {
174
+ Util.rmSync(this.options.cacheDir);
175
+ }
176
+
177
+ let reportPath = '';
178
+ if (coverageResults.reportPath) {
179
+ reportPath = EC.cyan(coverageResults.reportPath);
180
+ }
181
+ Util.logTime(`generated coverage reports: ${reportPath}`, time_start);
182
+
195
183
  return coverageResults;
196
184
  }
197
185
 
@@ -89,9 +89,11 @@ const saveIstanbulReports = (coverageData, fileSources, options) => {
89
89
 
90
90
  const istanbulGroup = options.reportGroup.istanbul;
91
91
  Object.keys(istanbulGroup).forEach((reportName) => {
92
+ const t1 = Date.now();
92
93
  const reportOptions = istanbulGroup[reportName];
93
94
  const report = istanbulReports.create(reportName, reportOptions);
94
95
  report.execute(context);
96
+ Util.logTime(`┌ [generate] saved report: ${reportName}`, t1);
95
97
  });
96
98
 
97
99
  // add watermarks and color