@vitest/coverage-istanbul 1.0.0-beta.6 → 1.0.1

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,31 +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
276
  const coverageMap = libCoverage.createCoverageMap({});
250
- for (const filename of uncoveredFiles) {
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);
251
281
  await this.ctx.vitenode.transformRequest(filename);
252
282
  const lastCoverage = this.instrumenter.lastFileCoverage();
253
283
  coverageMap.addFileCoverage(lastCoverage);
254
284
  }
255
- return coverageMap.data;
285
+ return coverageMap;
256
286
  }
257
287
  }
258
- async function mergeAndTransformCoverage(coverages) {
259
- const mergedCoverage = mergeCoverageMaps(...coverages);
260
- includeImplicitElseBranches(mergedCoverage);
288
+ async function transformCoverage(coverageMap) {
289
+ includeImplicitElseBranches(coverageMap);
261
290
  const sourceMapStore = libSourceMaps.createSourceMapStore();
262
- return await sourceMapStore.transformCoverage(mergedCoverage);
263
- }
264
- function mergeCoverageMaps(...coverageMaps) {
265
- return coverageMaps.reduce((coverage, previousCoverageMap) => {
266
- const map = libCoverage.createCoverageMap(coverage);
267
- map.merge(previousCoverageMap);
268
- return map;
269
- }, libCoverage.createCoverageMap({}));
291
+ return await sourceMapStore.transformCoverage(coverageMap);
270
292
  }
271
293
  function removeQueryParameters(filename) {
272
294
  return filename.split("?")[0];
@@ -295,5 +317,17 @@ function isEmptyCoverageRange(range) {
295
317
  function hasTerminalReporter(reporters) {
296
318
  return reporters.some(([reporter]) => reporter === "text" || reporter === "text-summary" || reporter === "text-lcov" || reporter === "teamcity");
297
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
+ }
298
332
 
299
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.6",
4
+ "version": "1.0.1",
5
5
  "description": "Istanbul coverage provider for Vitest",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -37,9 +37,10 @@
37
37
  "dist"
38
38
  ],
39
39
  "peerDependencies": {
40
- "vitest": "^1.0.0-0"
40
+ "vitest": "^1.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.6"
61
+ "vitest": "1.0.1"
60
62
  },
61
63
  "scripts": {
62
64
  "build": "rimraf dist && rollup -c",