supercov 0.0.20 → 0.0.21
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supercov",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"description": "Zero-edit, runner-aware coverage completeness for JavaScript test suites",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -68,12 +68,12 @@
|
|
|
68
68
|
"prepublishOnly": "npm run release:check"
|
|
69
69
|
},
|
|
70
70
|
"optionalDependencies": {
|
|
71
|
-
"@supercov/cli-darwin-arm64": "0.0.
|
|
72
|
-
"@supercov/cli-darwin-x64": "0.0.
|
|
73
|
-
"@supercov/cli-linux-arm64-gnu": "0.0.
|
|
74
|
-
"@supercov/cli-linux-arm64-musl": "0.0.
|
|
75
|
-
"@supercov/cli-linux-x64-gnu": "0.0.
|
|
76
|
-
"@supercov/cli-linux-x64-musl": "0.0.
|
|
71
|
+
"@supercov/cli-darwin-arm64": "0.0.21",
|
|
72
|
+
"@supercov/cli-darwin-x64": "0.0.21",
|
|
73
|
+
"@supercov/cli-linux-arm64-gnu": "0.0.21",
|
|
74
|
+
"@supercov/cli-linux-arm64-musl": "0.0.21",
|
|
75
|
+
"@supercov/cli-linux-x64-gnu": "0.0.21",
|
|
76
|
+
"@supercov/cli-linux-x64-musl": "0.0.21"
|
|
77
77
|
},
|
|
78
78
|
"peerDependencies": {
|
|
79
79
|
"@playwright/test": ">=1.55.0",
|
|
@@ -195,7 +195,7 @@ export function guestCoverageEnvironment(mapping, coverageEnvironment = process.
|
|
|
195
195
|
key,
|
|
196
196
|
value === undefined ? value : replaceRoot(value, mapping),
|
|
197
197
|
]));
|
|
198
|
-
const registerUrl = pathToFileURL(resolve(mapping.guestRoot, ".supercov/register.mjs")).href;
|
|
198
|
+
const registerUrl = pathToFileURL(resolve(mapping.guestRoot, ".supercov/node_modules/register.mjs")).href;
|
|
199
199
|
return {
|
|
200
200
|
...existingEnvironment,
|
|
201
201
|
...translated,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as native from "node:test";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import vm from "node:vm";
|
|
2
4
|
import { beginBufferedServerEvidence, flushBufferedServerEvidence, takeNodeAssertionPhases, withCoverageCarrier, } from "./runtime.js";
|
|
3
5
|
import { callerLocation, runnerExecutionScope, writeRunnerEvidence, } from "./runnerEvidence.js";
|
|
4
6
|
function callbackIndex(args) {
|
|
@@ -14,6 +16,76 @@ function testOptions(args, index) {
|
|
|
14
16
|
const candidate = args.slice(0, index).find((value) => value && typeof value === "object" && !Array.isArray(value));
|
|
15
17
|
return candidate;
|
|
16
18
|
}
|
|
19
|
+
// node:test derives a test's reported location from the direct caller of the
|
|
20
|
+
// registration call, and the adapter is that caller: every failing test
|
|
21
|
+
// reported "test at .../nodeTest.js". A compiled trampoline carrying the
|
|
22
|
+
// user's call site as its script origin registers the test instead, so the
|
|
23
|
+
// runner sees the location a direct call would have produced. The padded
|
|
24
|
+
// second line puts the call expression at the user's exact line and column.
|
|
25
|
+
const registrationSites = new Map();
|
|
26
|
+
function registrationAt(location) {
|
|
27
|
+
if (!location.file ||
|
|
28
|
+
location.line === undefined ||
|
|
29
|
+
location.column === undefined ||
|
|
30
|
+
location.line < 2)
|
|
31
|
+
return undefined;
|
|
32
|
+
const key = `${location.file}:${location.line}:${location.column}`;
|
|
33
|
+
let site = registrationSites.get(key);
|
|
34
|
+
if (site === undefined) {
|
|
35
|
+
try {
|
|
36
|
+
const filename = location.file.startsWith("file://")
|
|
37
|
+
? fileURLToPath(location.file)
|
|
38
|
+
: location.file;
|
|
39
|
+
site = vm.compileFunction(`return (\n${" ".repeat(Math.max(0, location.column - 1))}original(...args));`, ["original", "args"], { filename, lineOffset: location.line - 2 });
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
site = null;
|
|
43
|
+
}
|
|
44
|
+
registrationSites.set(key, site);
|
|
45
|
+
}
|
|
46
|
+
return site ?? undefined;
|
|
47
|
+
}
|
|
48
|
+
// A failing test's stack was captured while Supercov's wrappers were on the
|
|
49
|
+
// call path and while the code ran inside the mirrored workspace. Neither is
|
|
50
|
+
// part of the user's program: drop adapter frames and map workspace paths
|
|
51
|
+
// back to the source project so the report matches an uninstrumented run.
|
|
52
|
+
const restoredErrors = new WeakSet();
|
|
53
|
+
function restoreUserError(error, depth = 0) {
|
|
54
|
+
if (depth > 4 ||
|
|
55
|
+
!error ||
|
|
56
|
+
typeof error !== "object" ||
|
|
57
|
+
restoredErrors.has(error))
|
|
58
|
+
return error;
|
|
59
|
+
restoredErrors.add(error);
|
|
60
|
+
const workspaceRoot = process.env["SUPERCOV_PROJECT_ROOT"];
|
|
61
|
+
const sourceRoot = process.env["SUPERCOV_SOURCE_PROJECT_ROOT"];
|
|
62
|
+
const remap = (text) => workspaceRoot && sourceRoot && workspaceRoot !== sourceRoot
|
|
63
|
+
? text.split(workspaceRoot).join(sourceRoot)
|
|
64
|
+
: text;
|
|
65
|
+
try {
|
|
66
|
+
if (typeof error.stack === "string")
|
|
67
|
+
error.stack = remap(error.stack
|
|
68
|
+
.split("\n")
|
|
69
|
+
.filter((line) => !(line.trimStart().startsWith("at ") &&
|
|
70
|
+
line.includes("/.supercov/")))
|
|
71
|
+
.join("\n"));
|
|
72
|
+
if (typeof error.message === "string")
|
|
73
|
+
error.message = remap(error.message);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Frozen or accessor-backed errors keep their original form.
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
restoreUserError(error.cause, depth + 1);
|
|
80
|
+
if (Array.isArray(error.errors))
|
|
81
|
+
for (const aggregated of error.errors)
|
|
82
|
+
restoreUserError(aggregated, depth + 1);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// A throwing accessor never replaces the user's error.
|
|
86
|
+
}
|
|
87
|
+
return error;
|
|
88
|
+
}
|
|
17
89
|
function wrappedRegistration(original) {
|
|
18
90
|
const wrapped = function supercovNodeTest(...args) {
|
|
19
91
|
const index = callbackIndex(args);
|
|
@@ -72,8 +144,10 @@ function wrappedRegistration(original) {
|
|
|
72
144
|
try {
|
|
73
145
|
if (callback.length >= 2) {
|
|
74
146
|
const callbackDone = (error) => {
|
|
75
|
-
if (error)
|
|
147
|
+
if (error) {
|
|
76
148
|
status = "failed";
|
|
149
|
+
restoreUserError(error);
|
|
150
|
+
}
|
|
77
151
|
emit();
|
|
78
152
|
done?.(error);
|
|
79
153
|
};
|
|
@@ -87,7 +161,7 @@ function wrappedRegistration(original) {
|
|
|
87
161
|
}, (error) => {
|
|
88
162
|
status = "failed";
|
|
89
163
|
emit();
|
|
90
|
-
throw error;
|
|
164
|
+
throw restoreUserError(error);
|
|
91
165
|
});
|
|
92
166
|
emit();
|
|
93
167
|
return result;
|
|
@@ -95,7 +169,7 @@ function wrappedRegistration(original) {
|
|
|
95
169
|
catch (error) {
|
|
96
170
|
status = "failed";
|
|
97
171
|
emit();
|
|
98
|
-
throw error;
|
|
172
|
+
throw restoreUserError(error);
|
|
99
173
|
}
|
|
100
174
|
};
|
|
101
175
|
// node:test uses callback arity to distinguish promise/synchronous tests
|
|
@@ -107,6 +181,9 @@ function wrappedRegistration(original) {
|
|
|
107
181
|
: function supercovNodeTestCallback(context) {
|
|
108
182
|
return execute(this, context);
|
|
109
183
|
};
|
|
184
|
+
const registration = registrationAt(location);
|
|
185
|
+
if (registration)
|
|
186
|
+
return registration(this === undefined ? original : original.bind(this), next);
|
|
110
187
|
return Reflect.apply(original, this, next);
|
|
111
188
|
};
|
|
112
189
|
for (const property of ["skip", "todo", "only"]) {
|
|
@@ -120,14 +197,51 @@ function wrappedRegistration(original) {
|
|
|
120
197
|
}
|
|
121
198
|
return wrapped;
|
|
122
199
|
}
|
|
200
|
+
// Hooks carry no coverage evidence, but an error thrown inside one still
|
|
201
|
+
// reaches the report with the adapter's execution context in its stack.
|
|
202
|
+
// Restore it at the same boundary the test wrapper uses.
|
|
203
|
+
function restoringHook(original) {
|
|
204
|
+
return function supercovNodeTestHook(...args) {
|
|
205
|
+
const index = callbackIndex(args);
|
|
206
|
+
if (index < 0)
|
|
207
|
+
return Reflect.apply(original, this, args);
|
|
208
|
+
const callback = args[index];
|
|
209
|
+
const next = [...args];
|
|
210
|
+
// node:test uses callback arity to distinguish promise/synchronous
|
|
211
|
+
// hooks from the legacy done-callback form. Preserve it exactly.
|
|
212
|
+
next[index] = callback.length >= 2
|
|
213
|
+
? function supercovNodeTestHookDoneCallback(context, done) {
|
|
214
|
+
const restoringDone = (error) => {
|
|
215
|
+
if (error)
|
|
216
|
+
restoreUserError(error);
|
|
217
|
+
done?.(error);
|
|
218
|
+
};
|
|
219
|
+
return callback.call(this, context, restoringDone);
|
|
220
|
+
}
|
|
221
|
+
: function supercovNodeTestHookCallback(context) {
|
|
222
|
+
try {
|
|
223
|
+
const result = callback.call(this, context);
|
|
224
|
+
if (result && typeof result.then === "function")
|
|
225
|
+
return Promise.resolve(result).then(undefined, (error) => {
|
|
226
|
+
throw restoreUserError(error);
|
|
227
|
+
});
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
throw restoreUserError(error);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
return Reflect.apply(original, this, next);
|
|
235
|
+
};
|
|
236
|
+
}
|
|
123
237
|
export const test = wrappedRegistration(native.test);
|
|
124
238
|
export const it = wrappedRegistration(native.it);
|
|
125
239
|
export const suite = native.suite;
|
|
126
240
|
export const describe = native.describe;
|
|
127
|
-
export const before = native.before;
|
|
128
|
-
export const after = native.after;
|
|
129
|
-
export const beforeEach = native.beforeEach;
|
|
130
|
-
export const afterEach = native.afterEach;
|
|
241
|
+
export const before = restoringHook(native.before);
|
|
242
|
+
export const after = restoringHook(native.after);
|
|
243
|
+
export const beforeEach = restoringHook(native.beforeEach);
|
|
244
|
+
export const afterEach = restoringHook(native.afterEach);
|
|
131
245
|
export const mock = native.mock;
|
|
132
246
|
export const snapshot = native.snapshot;
|
|
133
247
|
export const run = native.run;
|
|
@@ -82,7 +82,7 @@ if (process.env.SUPERCOV_DURABLE_EVIDENCE_EACH_TEST === "1") {
|
|
|
82
82
|
const workerThreads = Module._load("node:worker_threads", undefined, false);
|
|
83
83
|
const NativeWorker = workerThreads.Worker;
|
|
84
84
|
const registerArgument = `--import=${new URL("./register.mjs", import.meta.url).href}`;
|
|
85
|
-
const appendRegister = values => values.some(value => value === registerArgument || value.includes("/.supercov/register.mjs"))
|
|
85
|
+
const appendRegister = values => values.some(value => value === registerArgument || value.includes("/.supercov/node_modules/register.mjs"))
|
|
86
86
|
? values
|
|
87
87
|
: [...values, registerArgument];
|
|
88
88
|
workerThreads.Worker = class SupercovWorker extends NativeWorker {
|
|
@@ -4,7 +4,7 @@ const GENERATED_TARGET = "__SUPERCOV_PLAYWRIGHT_MODULE__";
|
|
|
4
4
|
const TARGET = process.env.SUPERCOV_PLAYWRIGHT_MODULE ??
|
|
5
5
|
(GENERATED_TARGET.startsWith("__") ? "@playwright/test" : GENERATED_TARGET);
|
|
6
6
|
const REPLACEMENT = process.env.SUPERCOV_PLAYWRIGHT_WRAPPER ??
|
|
7
|
-
"./.supercov/playwright.js";
|
|
7
|
+
"./.supercov/node_modules/playwright.js";
|
|
8
8
|
const PROJECT_ROOT = process.env.SUPERCOV_PROJECT_ROOT;
|
|
9
9
|
const ORIGINAL_CONFIG = process.env.SUPERCOV_ORIGINAL_PLAYWRIGHT_CONFIG;
|
|
10
10
|
function belongsToProject(parentURL) {
|
|
@@ -29,7 +29,7 @@ export async function resolve(specifier, context, nextResolve) {
|
|
|
29
29
|
// source-local copy keeps strict rootDir compilers happy; this fallback
|
|
30
30
|
// resolves the emitted import to Supercov's generated runtime without
|
|
31
31
|
// requiring the project's build to copy our helper directory.
|
|
32
|
-
if (specifier.endsWith("/.supercov/runtime.js") &&
|
|
32
|
+
if (specifier.endsWith("/.supercov/node_modules/runtime.js") &&
|
|
33
33
|
belongsToProject(context.parentURL)) {
|
|
34
34
|
return {
|
|
35
35
|
url: new URL("./runtime.js", import.meta.url).href,
|