@vitest/coverage-v8 5.0.0-beta.5 → 5.0.0-beta.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/dist/browser.js CHANGED
@@ -13,18 +13,7 @@ const mod = {
13
13
  await triggerCommand("__vitest_startV8Coverage");
14
14
  },
15
15
  async takeCoverage() {
16
- const coverage = await triggerCommand("__vitest_takeV8Coverage");
17
- const result = [];
18
- // Reduce amount of data sent over rpc by doing some early result filtering
19
- for (const entry of coverage.result) {
20
- if (filterResult(entry)) {
21
- result.push({
22
- ...entry,
23
- url: decodeURIComponent(entry.url.replace(window.location.origin, ""))
24
- });
25
- }
26
- }
27
- return { result };
16
+ return triggerCommand("__vitest_takeV8Coverage", [window.location.href]);
28
17
  },
29
18
  stopCoverage() {
30
19
  // Browser mode should not stop coverage as same V8 instance is shared between tests
@@ -33,29 +22,5 @@ const mod = {
33
22
  return loadProvider();
34
23
  }
35
24
  };
36
- function filterResult(coverage) {
37
- if (!coverage.url.startsWith(window.location.origin)) {
38
- return false;
39
- }
40
- if (coverage.url.includes("/node_modules/")) {
41
- return false;
42
- }
43
- if (coverage.url.includes("__vitest_browser__")) {
44
- return false;
45
- }
46
- if (coverage.url.includes("__vitest__/assets")) {
47
- return false;
48
- }
49
- if (coverage.url === window.location.href) {
50
- return false;
51
- }
52
- if (coverage.url.includes("/@id/@vitest/")) {
53
- return false;
54
- }
55
- if (coverage.url.includes("/@vite/client")) {
56
- return false;
57
- }
58
- return true;
59
- }
60
25
 
61
26
  export { mod as default };
@@ -0,0 +1,196 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { existsSync } from 'node:fs';
3
+ import { writeFile } from 'node:fs/promises';
4
+
5
+ const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
6
+ function normalizeWindowsPath(input = "") {
7
+ if (!input) {
8
+ return input;
9
+ }
10
+ return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
11
+ }
12
+
13
+ const _UNC_REGEX = /^[/\\]{2}/;
14
+ const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
15
+ const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
16
+ const normalize = function(path) {
17
+ if (path.length === 0) {
18
+ return ".";
19
+ }
20
+ path = normalizeWindowsPath(path);
21
+ const isUNCPath = path.match(_UNC_REGEX);
22
+ const isPathAbsolute = isAbsolute(path);
23
+ const trailingSeparator = path[path.length - 1] === "/";
24
+ path = normalizeString(path, !isPathAbsolute);
25
+ if (path.length === 0) {
26
+ if (isPathAbsolute) {
27
+ return "/";
28
+ }
29
+ return trailingSeparator ? "./" : ".";
30
+ }
31
+ if (trailingSeparator) {
32
+ path += "/";
33
+ }
34
+ if (_DRIVE_LETTER_RE.test(path)) {
35
+ path += "/";
36
+ }
37
+ if (isUNCPath) {
38
+ if (!isPathAbsolute) {
39
+ return `//./${path}`;
40
+ }
41
+ return `//${path}`;
42
+ }
43
+ return isPathAbsolute && !isAbsolute(path) ? `/${path}` : path;
44
+ };
45
+ function cwd() {
46
+ if (typeof process !== "undefined" && typeof process.cwd === "function") {
47
+ return process.cwd().replace(/\\/g, "/");
48
+ }
49
+ return "/";
50
+ }
51
+ const resolve = function(...arguments_) {
52
+ arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));
53
+ let resolvedPath = "";
54
+ let resolvedAbsolute = false;
55
+ for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {
56
+ const path = index >= 0 ? arguments_[index] : cwd();
57
+ if (!path || path.length === 0) {
58
+ continue;
59
+ }
60
+ resolvedPath = `${path}/${resolvedPath}`;
61
+ resolvedAbsolute = isAbsolute(path);
62
+ }
63
+ resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
64
+ if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
65
+ return `/${resolvedPath}`;
66
+ }
67
+ return resolvedPath.length > 0 ? resolvedPath : ".";
68
+ };
69
+ function normalizeString(path, allowAboveRoot) {
70
+ let res = "";
71
+ let lastSegmentLength = 0;
72
+ let lastSlash = -1;
73
+ let dots = 0;
74
+ let char = null;
75
+ for (let index = 0; index <= path.length; ++index) {
76
+ if (index < path.length) {
77
+ char = path[index];
78
+ } else if (char === "/") {
79
+ break;
80
+ } else {
81
+ char = "/";
82
+ }
83
+ if (char === "/") {
84
+ if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) {
85
+ if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
86
+ if (res.length > 2) {
87
+ const lastSlashIndex = res.lastIndexOf("/");
88
+ if (lastSlashIndex === -1) {
89
+ res = "";
90
+ lastSegmentLength = 0;
91
+ } else {
92
+ res = res.slice(0, lastSlashIndex);
93
+ lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
94
+ }
95
+ lastSlash = index;
96
+ dots = 0;
97
+ continue;
98
+ } else if (res.length > 0) {
99
+ res = "";
100
+ lastSegmentLength = 0;
101
+ lastSlash = index;
102
+ dots = 0;
103
+ continue;
104
+ }
105
+ }
106
+ if (allowAboveRoot) {
107
+ res += res.length > 0 ? "/.." : "..";
108
+ lastSegmentLength = 2;
109
+ }
110
+ } else {
111
+ if (res.length > 0) {
112
+ res += `/${path.slice(lastSlash + 1, index)}`;
113
+ } else {
114
+ res = path.slice(lastSlash + 1, index);
115
+ }
116
+ lastSegmentLength = index - lastSlash - 1;
117
+ }
118
+ lastSlash = index;
119
+ dots = 0;
120
+ } else if (char === "." && dots !== -1) {
121
+ ++dots;
122
+ } else {
123
+ dots = -1;
124
+ }
125
+ }
126
+ return res;
127
+ }
128
+ const isAbsolute = function(p) {
129
+ return _IS_ABSOLUTE_RE.test(p);
130
+ };
131
+
132
+ const commands = {
133
+ startV8Coverage,
134
+ takeV8Coverage
135
+ };
136
+ async function startV8Coverage(context) {
137
+ const session = await context.__ensureCDPHandler();
138
+ await session.send("Profiler.enable");
139
+ await session.send("Profiler.startPreciseCoverage", {
140
+ callCount: true,
141
+ detailed: true
142
+ });
143
+ }
144
+ async function takeV8Coverage(context, pageUrl) {
145
+ const session = await context.__ensureCDPHandler();
146
+ const coverage = await session.send("Profiler.takePreciseCoverage");
147
+ const origin = new URL(pageUrl).origin;
148
+ const result = [];
149
+ for (const entry of coverage.result) {
150
+ if (filterResult(entry.url, origin, pageUrl)) {
151
+ entry.url = decodeURIComponent(entry.url.replace(origin, ""));
152
+ result.push(entry);
153
+ }
154
+ }
155
+ const provider = context.project.vitest.coverageProvider;
156
+ return await writeCoverageFile(provider.coverageFilesDirectory, { result });
157
+ }
158
+ async function writeCoverageFile(coverageFilesDirectory, coverage) {
159
+ // Write results on file system directly and transfer only the filename over RPC
160
+ const filename = resolve(coverageFilesDirectory, `coverage-${randomUUID()}.json`);
161
+ try {
162
+ await writeFile(filename, JSON.stringify(coverage), "utf-8");
163
+ } catch (error) {
164
+ if (!existsSync(coverageFilesDirectory)) {
165
+ throw new Error(`Something removed the coverage directory "${coverageFilesDirectory}" Vitest created earlier. Make sure you are not running multiple Vitests with the same "coverage.reportsDirectory" at the same time.`, { cause: error });
166
+ }
167
+ throw error;
168
+ }
169
+ return filename;
170
+ }
171
+ function filterResult(url, origin, pageUrl) {
172
+ if (!url.startsWith(origin)) {
173
+ return false;
174
+ }
175
+ if (url.includes("/node_modules/")) {
176
+ return false;
177
+ }
178
+ if (url.includes("__vitest_browser__")) {
179
+ return false;
180
+ }
181
+ if (url.includes("__vitest__/assets")) {
182
+ return false;
183
+ }
184
+ if (url === pageUrl) {
185
+ return false;
186
+ }
187
+ if (url.includes("/@id/@vitest/")) {
188
+ return false;
189
+ }
190
+ if (url.includes("/@vite/client")) {
191
+ return false;
192
+ }
193
+ return true;
194
+ }
195
+
196
+ export { commands as c, normalize as n, writeCoverageFile as w };
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import assert from 'node:assert';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import { existsSync } from 'node:fs';
3
4
  import { readdir, readFile, rm } from 'node:fs/promises';
@@ -5,8 +6,8 @@ import inspector from 'node:inspector/promises';
5
6
  import { resolve } from 'node:path';
6
7
  import { fileURLToPath } from 'node:url';
7
8
  import { provider } from 'std-env';
9
+ import { n as normalize, w as writeCoverageFile } from './commands-wCiafWY1.js';
8
10
  import { l as loadProvider } from './load-provider-CdgAx3rL.js';
9
- import { n as normalize } from './pathe.M-eThtNZ-BTaAGrLg.js';
10
11
 
11
12
  let enabled = false;
12
13
  const mod = {
@@ -32,7 +33,7 @@ const mod = {
32
33
  },
33
34
  async takeCoverage(options) {
34
35
  if (provider === "stackblitz") {
35
- return { result: [] };
36
+ return;
36
37
  }
37
38
  const session = this.session;
38
39
  if (!session) {
@@ -66,7 +67,9 @@ const mod = {
66
67
  }
67
68
  }
68
69
  }
69
- return { result };
70
+ const coverageFilesDirectory = options?.coverageFilesDirectory;
71
+ assert(coverageFilesDirectory, "coverageFilesDirectory is required");
72
+ return await writeCoverageFile(coverageFilesDirectory, { result });
70
73
  },
71
74
  async stopCoverage({ isolate }) {
72
75
  if (isolate === false) {
package/dist/provider.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, promises } from 'node:fs';
2
2
  import { fileURLToPath } from 'node:url';
3
- import { mergeProcessCovs } from '@bcoe/v8-coverage';
3
+ import { mergeScriptCovs } from '@bcoe/v8-coverage';
4
4
  import astV8ToIstanbul from 'ast-v8-to-istanbul';
5
5
  import libCoverage from 'istanbul-lib-coverage';
6
6
  import libReport from 'istanbul-lib-report';
@@ -10,9 +10,11 @@ import { createDebug } from 'obug';
10
10
  import { provider } from 'std-env';
11
11
  import c from 'tinyrainbow';
12
12
  import { BaseCoverageProvider, parseAstAsync } from 'vitest/node';
13
- import { n as normalize } from './pathe.M-eThtNZ-BTaAGrLg.js';
13
+ import { c as commands, n as normalize } from './commands-wCiafWY1.js';
14
+ import 'node:crypto';
15
+ import 'node:fs/promises';
14
16
 
15
- var version = "5.0.0-beta.5";
17
+ var version = "5.0.0-beta.7";
16
18
 
17
19
  const FILE_PROTOCOL = "file://";
18
20
  const debug = createDebug("vitest:coverage");
@@ -21,6 +23,13 @@ class V8CoverageProvider extends BaseCoverageProvider {
21
23
  version = version;
22
24
  initialize(ctx) {
23
25
  this._initialize(ctx);
26
+ for (const project of ctx.projects) {
27
+ if (project.isBrowserEnabled() && project.browser) {
28
+ for (const [name, command] of Object.entries(commands)) {
29
+ project.browser.registerCommand(`__vitest_${name}`, command);
30
+ }
31
+ }
32
+ }
24
33
  if (this.options.autoAttachSubprocess) {
25
34
  const isAnyThreadsPools = ctx.projects.some((p) => p.config.pool === "threads" || p.config.pool === "vmThreads");
26
35
  if (isAnyThreadsPools) {
@@ -38,31 +47,28 @@ class V8CoverageProvider extends BaseCoverageProvider {
38
47
  async generateCoverage({ allTestsRun }) {
39
48
  const start = debug.enabled ? performance.now() : 0;
40
49
  const coverageMap = this.createCoverageMap();
41
- let merged = { result: [] };
50
+ const mergedScripts = new Map();
42
51
  const autoAttachSubprocess = this.options.autoAttachSubprocess;
43
52
  await this.readCoverageFiles({
44
53
  onFileRead(coverage) {
45
- merged = mergeProcessCovs([merged, coverage]);
46
- // mergeProcessCovs sometimes loses autoAttachSubprocess
47
- const fromExtendedContext = autoAttachSubprocess ? coverage.result.filter((r) => r.isExtendedContext) : [];
48
- // mergeProcessCovs sometimes loses startOffset, e.g. in vue
49
- merged.result.forEach((result) => {
50
- if (!result.startOffset) {
51
- const original = coverage.result.find((r) => r.url === result.url);
52
- result.startOffset = original?.startOffset || 0;
53
- }
54
- if (autoAttachSubprocess && !result.isExtendedContext) {
55
- const actual = fromExtendedContext.find((r) => r.url === result.url);
56
- result.isExtendedContext = actual?.isExtendedContext;
54
+ for (const script of coverage.result) {
55
+ const previous = mergedScripts.get(script.url);
56
+ const merged = mergeScriptCovs(previous ? [previous, script] : [script]);
57
+ const startOffset = previous?.startOffset || script.startOffset || 0;
58
+ const isExtendedContext = previous?.isExtendedContext || script.isExtendedContext;
59
+ merged.startOffset ||= startOffset;
60
+ if (autoAttachSubprocess && isExtendedContext) {
61
+ merged.isExtendedContext = true;
57
62
  }
58
- });
63
+ mergedScripts.set(merged.url, merged);
64
+ }
59
65
  },
60
66
  onFinished: async (project, environment) => {
61
67
  // Source maps can change based on projectName and transform mode.
62
68
  // Coverage transform re-uses source maps so we need to separate transforms from each other.
63
- const converted = await this.convertCoverage(merged, project, environment);
69
+ const converted = await this.convertCoverage({ result: Array.from(mergedScripts.values()) }, project, environment);
64
70
  coverageMap.merge(converted);
65
- merged = { result: [] };
71
+ mergedScripts.clear();
66
72
  },
67
73
  onDebug: debug
68
74
  });
@@ -208,7 +214,7 @@ class V8CoverageProvider extends BaseCoverageProvider {
208
214
  }
209
215
  async getSources(url, onTransform, functions = [], isExtendedContext = false) {
210
216
  // TODO: need to standardize file urls before this call somehow, this is messy
211
- const filepath = url.match(/^file:\/\/\/\w:\//) ? url.slice(8) : removeStartsWith(url, FILE_PROTOCOL);
217
+ const filepath = /^file:\/\/\/\w:\//.test(url) ? url.slice(8) : removeStartsWith(url, FILE_PROTOCOL);
212
218
  // TODO: do we still need to "catch" here? why would it fail?
213
219
  const transformResult = await onTransform(filepath, isExtendedContext).catch(() => null);
214
220
  const map = transformResult?.map;
@@ -237,12 +243,9 @@ class V8CoverageProvider extends BaseCoverageProvider {
237
243
  };
238
244
  }
239
245
  async convertCoverage(coverage, project = this.ctx.getRootProject(), environment) {
240
- if (environment === "__browser__" && !project.browser) {
241
- throw new Error(`Cannot access browser module graph because it was torn down.`);
242
- }
243
246
  const onTransform = async (filepath, isExtendedContext = false) => {
244
247
  const result = await this.transformFile(filepath, project, environment, !isExtendedContext);
245
- if (result && environment === "__browser__" && project.browser) {
248
+ if (result && project.isBrowserEnabled()) {
246
249
  return {
247
250
  ...result,
248
251
  code: `${result.code}// <inline-source-map>`
@@ -252,7 +255,7 @@ class V8CoverageProvider extends BaseCoverageProvider {
252
255
  };
253
256
  const scriptCoverages = [];
254
257
  for (const result of coverage.result) {
255
- if (environment === "__browser__") {
258
+ if (environment === "client" && project.isBrowserEnabled()) {
256
259
  if (result.url.startsWith("/@fs")) {
257
260
  result.url = `${FILE_PROTOCOL}${removeStartsWith(result.url, "/@fs")}`;
258
261
  } else if (result.url.startsWith(project.config.root)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitest/coverage-v8",
3
3
  "type": "module",
4
- "version": "5.0.0-beta.5",
4
+ "version": "5.0.0-beta.7",
5
5
  "description": "V8 coverage provider for Vitest",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -41,8 +41,8 @@
41
41
  "dist"
42
42
  ],
43
43
  "peerDependencies": {
44
- "@vitest/browser": "5.0.0-beta.5",
45
- "vitest": "5.0.0-beta.5"
44
+ "@vitest/browser": "5.0.0-beta.7",
45
+ "vitest": "5.0.0-beta.7"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@vitest/browser": {
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@bcoe/v8-coverage": "^1.0.2",
54
- "ast-v8-to-istanbul": "^1.0.0",
54
+ "ast-v8-to-istanbul": "^1.0.5",
55
55
  "istanbul-lib-coverage": "^3.2.2",
56
56
  "istanbul-lib-report": "^3.0.1",
57
57
  "istanbul-reports": "^3.2.0",
@@ -59,16 +59,16 @@
59
59
  "obug": "^2.1.1",
60
60
  "std-env": "^4.0.0-rc.1",
61
61
  "tinyrainbow": "^3.1.0",
62
- "@vitest/utils": "5.0.0-beta.5"
62
+ "@vitest/utils": "5.0.0-beta.7"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/istanbul-lib-coverage": "^2.0.6",
66
66
  "@types/istanbul-lib-report": "^3.0.3",
67
67
  "@types/istanbul-reports": "^3.0.4",
68
68
  "pathe": "^2.0.3",
69
- "vitest": "5.0.0-beta.5",
70
- "@vitest/browser-playwright": "5.0.0-beta.5",
71
- "@vitest/browser": "5.0.0-beta.5"
69
+ "@vitest/browser-playwright": "5.0.0-beta.7",
70
+ "@vitest/browser": "5.0.0-beta.7",
71
+ "vitest": "5.0.0-beta.7"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "premove dist && rollup -c",
@@ -1,104 +0,0 @@
1
- const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
2
- function normalizeWindowsPath(input = "") {
3
- if (!input) {
4
- return input;
5
- }
6
- return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
7
- }
8
-
9
- const _UNC_REGEX = /^[/\\]{2}/;
10
- const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
11
- const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
12
- const normalize = function(path) {
13
- if (path.length === 0) {
14
- return ".";
15
- }
16
- path = normalizeWindowsPath(path);
17
- const isUNCPath = path.match(_UNC_REGEX);
18
- const isPathAbsolute = isAbsolute(path);
19
- const trailingSeparator = path[path.length - 1] === "/";
20
- path = normalizeString(path, !isPathAbsolute);
21
- if (path.length === 0) {
22
- if (isPathAbsolute) {
23
- return "/";
24
- }
25
- return trailingSeparator ? "./" : ".";
26
- }
27
- if (trailingSeparator) {
28
- path += "/";
29
- }
30
- if (_DRIVE_LETTER_RE.test(path)) {
31
- path += "/";
32
- }
33
- if (isUNCPath) {
34
- if (!isPathAbsolute) {
35
- return `//./${path}`;
36
- }
37
- return `//${path}`;
38
- }
39
- return isPathAbsolute && !isAbsolute(path) ? `/${path}` : path;
40
- };
41
- function normalizeString(path, allowAboveRoot) {
42
- let res = "";
43
- let lastSegmentLength = 0;
44
- let lastSlash = -1;
45
- let dots = 0;
46
- let char = null;
47
- for (let index = 0; index <= path.length; ++index) {
48
- if (index < path.length) {
49
- char = path[index];
50
- } else if (char === "/") {
51
- break;
52
- } else {
53
- char = "/";
54
- }
55
- if (char === "/") {
56
- if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) {
57
- if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
58
- if (res.length > 2) {
59
- const lastSlashIndex = res.lastIndexOf("/");
60
- if (lastSlashIndex === -1) {
61
- res = "";
62
- lastSegmentLength = 0;
63
- } else {
64
- res = res.slice(0, lastSlashIndex);
65
- lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
66
- }
67
- lastSlash = index;
68
- dots = 0;
69
- continue;
70
- } else if (res.length > 0) {
71
- res = "";
72
- lastSegmentLength = 0;
73
- lastSlash = index;
74
- dots = 0;
75
- continue;
76
- }
77
- }
78
- if (allowAboveRoot) {
79
- res += res.length > 0 ? "/.." : "..";
80
- lastSegmentLength = 2;
81
- }
82
- } else {
83
- if (res.length > 0) {
84
- res += `/${path.slice(lastSlash + 1, index)}`;
85
- } else {
86
- res = path.slice(lastSlash + 1, index);
87
- }
88
- lastSegmentLength = index - lastSlash - 1;
89
- }
90
- lastSlash = index;
91
- dots = 0;
92
- } else if (char === "." && dots !== -1) {
93
- ++dots;
94
- } else {
95
- dots = -1;
96
- }
97
- }
98
- return res;
99
- }
100
- const isAbsolute = function(p) {
101
- return _IS_ABSOLUTE_RE.test(p);
102
- };
103
-
104
- export { normalize as n };