@vitest/coverage-istanbul 1.0.0-beta.5 → 1.0.0

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.
@@ -1,10 +1,11 @@
1
1
  import { CoverageProvider, Vitest, AfterSuiteRunMeta, ReportContext, ResolvedCoverageOptions } from 'vitest';
2
2
  import { BaseCoverageProvider } from 'vitest/coverage';
3
- import { CoverageMapData } from 'istanbul-lib-coverage';
3
+ import { CoverageMap } from 'istanbul-lib-coverage';
4
4
  import { Instrumenter } from 'istanbul-lib-instrument';
5
5
 
6
6
  type Options = ResolvedCoverageOptions<'istanbul'>;
7
- type CoverageByTransformMode = Record<AfterSuiteRunMeta['transformMode'], CoverageMapData[]>;
7
+ type Filename = string;
8
+ type CoverageFilesByTransformMode = Record<AfterSuiteRunMeta['transformMode'], Filename[]>;
8
9
  type ProjectName = NonNullable<AfterSuiteRunMeta['projectName']> | typeof DEFAULT_PROJECT;
9
10
  interface TestExclude {
10
11
  new (opts: {
@@ -26,13 +27,9 @@ declare class IstanbulCoverageProvider extends BaseCoverageProvider implements C
26
27
  options: Options;
27
28
  instrumenter: Instrumenter;
28
29
  testExclude: InstanceType<TestExclude>;
29
- /**
30
- * Coverage objects collected from workers.
31
- * Some istanbul utilizers write these into file system instead of storing in memory.
32
- * If storing in memory causes issues, we can simply write these into fs in `onAfterSuiteRun`
33
- * and read them back when merging coverage objects in `onAfterAllFilesRun`.
34
- */
35
- coverages: Map<ProjectName, CoverageByTransformMode>;
30
+ coverageFiles: Map<ProjectName, CoverageFilesByTransformMode>;
31
+ coverageFilesDirectory: string;
32
+ pendingPromises: Promise<void>[];
36
33
  initialize(ctx: Vitest): void;
37
34
  resolveOptions(): Options;
38
35
  onFileTransform(sourceCode: string, id: string, pluginCtx: any): {
@@ -42,7 +39,7 @@ declare class IstanbulCoverageProvider extends BaseCoverageProvider implements C
42
39
  onAfterSuiteRun({ coverage, transformMode, projectName }: AfterSuiteRunMeta): void;
43
40
  clean(clean?: boolean): Promise<void>;
44
41
  reportCoverage({ allTestsRun }?: ReportContext): Promise<void>;
45
- getCoverageMapForUncoveredFiles(coveredFiles: string[]): Promise<CoverageMapData>;
42
+ getCoverageMapForUncoveredFiles(coveredFiles: string[]): Promise<CoverageMap>;
46
43
  }
47
44
 
48
45
  export { IstanbulCoverageProvider };
package/dist/provider.js CHANGED
@@ -1,8 +1,9 @@
1
- import { existsSync, promises, writeFileSync } from 'node:fs';
1
+ import { promises, existsSync, writeFileSync } from 'node:fs';
2
2
  import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config';
3
3
  import { BaseCoverageProvider } from 'vitest/coverage';
4
4
  import c from 'picocolors';
5
5
  import { parseModule } from 'magicast';
6
+ import createDebug from 'debug';
6
7
  import libReport from 'istanbul-lib-report';
7
8
  import reports from 'istanbul-reports';
8
9
  import libCoverage from 'istanbul-lib-coverage';
@@ -106,19 +107,17 @@ const isAbsolute = function(p) {
106
107
  };
107
108
 
108
109
  const DEFAULT_PROJECT = Symbol.for("default-project");
110
+ const debug = createDebug("vitest:coverage");
111
+ let uniqueId = 0;
109
112
  class IstanbulCoverageProvider extends BaseCoverageProvider {
110
113
  name = "istanbul";
111
114
  ctx;
112
115
  options;
113
116
  instrumenter;
114
117
  testExclude;
115
- /**
116
- * Coverage objects collected from workers.
117
- * Some istanbul utilizers write these into file system instead of storing in memory.
118
- * If storing in memory causes issues, we can simply write these into fs in `onAfterSuiteRun`
119
- * and read them back when merging coverage objects in `onAfterAllFilesRun`.
120
- */
121
- coverages = /* @__PURE__ */ new Map();
118
+ coverageFiles = /* @__PURE__ */ new Map();
119
+ coverageFilesDirectory;
120
+ pendingPromises = [];
122
121
  initialize(ctx) {
123
122
  const config = ctx.config.coverage;
124
123
  this.ctx = ctx;
@@ -157,6 +156,7 @@ class IstanbulCoverageProvider extends BaseCoverageProvider {
157
156
  extension: this.options.extension,
158
157
  relativePath: !this.options.allowExternal
159
158
  });
159
+ this.coverageFilesDirectory = resolve(this.options.reportsDirectory, ".tmp");
160
160
  }
161
161
  resolveOptions() {
162
162
  return this.options;
@@ -176,33 +176,58 @@ class IstanbulCoverageProvider extends BaseCoverageProvider {
176
176
  * backwards compatibility is a breaking change.
177
177
  */
178
178
  onAfterSuiteRun({ coverage, transformMode, projectName }) {
179
+ if (!coverage)
180
+ return;
179
181
  if (transformMode !== "web" && transformMode !== "ssr")
180
182
  throw new Error(`Invalid transform mode: ${transformMode}`);
181
- let entry = this.coverages.get(projectName || DEFAULT_PROJECT);
183
+ let entry = this.coverageFiles.get(projectName || DEFAULT_PROJECT);
182
184
  if (!entry) {
183
185
  entry = { web: [], ssr: [] };
184
- this.coverages.set(projectName || DEFAULT_PROJECT, entry);
186
+ this.coverageFiles.set(projectName || DEFAULT_PROJECT, entry);
185
187
  }
186
- entry[transformMode].push(coverage);
188
+ const filename = resolve(this.coverageFilesDirectory, `coverage-${uniqueId++}.json`);
189
+ entry[transformMode].push(filename);
190
+ const promise = promises.writeFile(filename, JSON.stringify(coverage), "utf-8");
191
+ this.pendingPromises.push(promise);
187
192
  }
188
193
  async clean(clean = true) {
189
194
  if (clean && existsSync(this.options.reportsDirectory))
190
195
  await promises.rm(this.options.reportsDirectory, { recursive: true, force: true, maxRetries: 10 });
191
- this.coverages = /* @__PURE__ */ new Map();
196
+ if (existsSync(this.coverageFilesDirectory))
197
+ await promises.rm(this.coverageFilesDirectory, { recursive: true, force: true, maxRetries: 10 });
198
+ await promises.mkdir(this.coverageFilesDirectory, { recursive: true });
199
+ this.coverageFiles = /* @__PURE__ */ new Map();
200
+ this.pendingPromises = [];
192
201
  }
193
202
  async reportCoverage({ allTestsRun } = {}) {
194
- const coverageMaps = await Promise.all(
195
- Array.from(this.coverages.values()).map((coverages) => [
196
- mergeAndTransformCoverage(coverages.ssr),
197
- mergeAndTransformCoverage(coverages.web)
198
- ]).flat()
199
- );
203
+ const coverageMap = libCoverage.createCoverageMap({});
204
+ let index = 0;
205
+ const total = this.pendingPromises.length;
206
+ await Promise.all(this.pendingPromises);
207
+ this.pendingPromises = [];
208
+ for (const coveragePerProject of this.coverageFiles.values()) {
209
+ for (const filenames of [coveragePerProject.ssr, coveragePerProject.web]) {
210
+ const coverageMapByTransformMode = libCoverage.createCoverageMap({});
211
+ for (const chunk of toSlices(filenames, this.options.processingConcurrency)) {
212
+ if (debug.enabled) {
213
+ index += chunk.length;
214
+ debug("Covered files %d/%d", index, total);
215
+ }
216
+ await Promise.all(chunk.map(async (filename) => {
217
+ const contents = await promises.readFile(filename, "utf-8");
218
+ const coverage = JSON.parse(contents);
219
+ coverageMapByTransformMode.merge(coverage);
220
+ }));
221
+ }
222
+ const transformedCoverage = await transformCoverage(coverageMapByTransformMode);
223
+ coverageMap.merge(transformedCoverage);
224
+ }
225
+ }
200
226
  if (this.options.all && allTestsRun) {
201
- const coveredFiles = coverageMaps.map((map) => map.files()).flat();
227
+ const coveredFiles = coverageMap.files();
202
228
  const uncoveredCoverage = await this.getCoverageMapForUncoveredFiles(coveredFiles);
203
- coverageMaps.push(await mergeAndTransformCoverage([uncoveredCoverage]));
229
+ coverageMap.merge(await transformCoverage(uncoveredCoverage));
204
230
  }
205
- const coverageMap = mergeCoverageMaps(...coverageMaps);
206
231
  const context = libReport.createContext({
207
232
  dir: this.options.reportsDirectory,
208
233
  coverageMap,
@@ -242,43 +267,28 @@ class IstanbulCoverageProvider extends BaseCoverageProvider {
242
267
  });
243
268
  }
244
269
  }
270
+ await promises.rm(this.coverageFilesDirectory, { recursive: true });
271
+ this.coverageFiles = /* @__PURE__ */ new Map();
245
272
  }
246
273
  async getCoverageMapForUncoveredFiles(coveredFiles) {
247
274
  const includedFiles = await this.testExclude.glob(this.ctx.config.root);
248
275
  const uncoveredFiles = includedFiles.map((file) => resolve(this.ctx.config.root, file)).filter((file) => !coveredFiles.includes(file));
249
- const transformResults = await Promise.all(uncoveredFiles.map(async (filename) => {
250
- const transformResult = await this.ctx.vitenode.transformRequest(filename);
251
- return { transformResult, filename };
252
- }));
253
276
  const coverageMap = libCoverage.createCoverageMap({});
254
- for (const { transformResult, filename } of transformResults) {
255
- const sourceMap = transformResult?.map;
256
- if (sourceMap) {
257
- this.instrumenter.instrumentSync(
258
- transformResult.code,
259
- filename,
260
- sourceMap
261
- );
262
- const lastCoverage = this.instrumenter.lastFileCoverage();
263
- if (lastCoverage)
264
- coverageMap.addFileCoverage(lastCoverage);
265
- }
277
+ for (const [index, filename] of uncoveredFiles.entries()) {
278
+ debug("Uncovered file %s %d/%d", filename, index, uncoveredFiles.length);
279
+ if (this.ctx.vitenode.fetchCache.has(filename))
280
+ this.ctx.vitenode.fetchCache.delete(filename);
281
+ await this.ctx.vitenode.transformRequest(filename);
282
+ const lastCoverage = this.instrumenter.lastFileCoverage();
283
+ coverageMap.addFileCoverage(lastCoverage);
266
284
  }
267
- return coverageMap.data;
285
+ return coverageMap;
268
286
  }
269
287
  }
270
- async function mergeAndTransformCoverage(coverages) {
271
- const mergedCoverage = mergeCoverageMaps(...coverages);
272
- includeImplicitElseBranches(mergedCoverage);
288
+ async function transformCoverage(coverageMap) {
289
+ includeImplicitElseBranches(coverageMap);
273
290
  const sourceMapStore = libSourceMaps.createSourceMapStore();
274
- return await sourceMapStore.transformCoverage(mergedCoverage);
275
- }
276
- function mergeCoverageMaps(...coverageMaps) {
277
- return coverageMaps.reduce((coverage, previousCoverageMap) => {
278
- const map = libCoverage.createCoverageMap(coverage);
279
- map.merge(previousCoverageMap);
280
- return map;
281
- }, libCoverage.createCoverageMap({}));
291
+ return await sourceMapStore.transformCoverage(coverageMap);
282
292
  }
283
293
  function removeQueryParameters(filename) {
284
294
  return filename.split("?")[0];
@@ -307,5 +317,17 @@ function isEmptyCoverageRange(range) {
307
317
  function hasTerminalReporter(reporters) {
308
318
  return reporters.some(([reporter]) => reporter === "text" || reporter === "text-summary" || reporter === "text-lcov" || reporter === "teamcity");
309
319
  }
320
+ function toSlices(array, size) {
321
+ return array.reduce((chunks, item) => {
322
+ const index = Math.max(0, chunks.length - 1);
323
+ const lastChunk = chunks[index] || [];
324
+ chunks[index] = lastChunk;
325
+ if (lastChunk.length >= size)
326
+ chunks.push([item]);
327
+ else
328
+ lastChunk.push(item);
329
+ return chunks;
330
+ }, []);
331
+ }
310
332
 
311
333
  export { IstanbulCoverageProvider };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitest/coverage-istanbul",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.5",
4
+ "version": "1.0.0",
5
5
  "description": "Istanbul coverage provider for Vitest",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -40,6 +40,7 @@
40
40
  "vitest": "^1.0.0-0"
41
41
  },
42
42
  "dependencies": {
43
+ "debug": "^4.3.4",
43
44
  "istanbul-lib-coverage": "^3.2.2",
44
45
  "istanbul-lib-instrument": "^6.0.1",
45
46
  "istanbul-lib-report": "^3.0.1",
@@ -50,13 +51,14 @@
50
51
  "test-exclude": "^6.0.0"
51
52
  },
52
53
  "devDependencies": {
54
+ "@types/debug": "^4.1.12",
53
55
  "@types/istanbul-lib-coverage": "^2.0.6",
54
56
  "@types/istanbul-lib-instrument": "^1.7.7",
55
57
  "@types/istanbul-lib-report": "^3.0.3",
56
58
  "@types/istanbul-lib-source-maps": "^4.0.4",
57
59
  "@types/istanbul-reports": "^3.0.4",
58
60
  "pathe": "^1.1.1",
59
- "vitest": "1.0.0-beta.5"
61
+ "vitest": "1.0.0"
60
62
  },
61
63
  "scripts": {
62
64
  "build": "rimraf dist && rollup -c",