supercov 4.20.3 → 4.22.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.
- package/README.md +1 -0
- package/docs/cli.md +2 -0
- package/docs/supported-suites.md +23 -0
- package/package.json +9 -9
- package/runtime/javascript/bunTest.mjs +185 -0
- package/runtime/javascript/provenance.mjs +1 -1
- package/runtime/javascript/register.mjs +30 -0
- package/runtime/javascript/resolve-loader.mjs +26 -1
- package/runtime/javascript/runnerEvidence.mjs +7 -3
- package/runtime/javascript/runtime.mjs +4 -0
package/README.md
CHANGED
|
@@ -411,3 +411,4 @@ Ask questions, share what you measured, and talk to the people building Supercov
|
|
|
411
411
|
- [@Uhbif2](https://github.com/Uhbif2)
|
|
412
412
|
- [@maik-intellicoach](https://github.com/maik-intellicoach)
|
|
413
413
|
- [@untitaker](https://github.com/untitaker)
|
|
414
|
+
- [@artile](https://github.com/artile)
|
package/docs/cli.md
CHANGED
|
@@ -926,6 +926,7 @@ terminal or offline environment after the package has been downloaded.
|
|
|
926
926
|
| `SUPERCOV_RUN_ID` | Set by Supercov, not by you: in every process a measured command starts, the run's id, so a test or harness can tell it is measured |
|
|
927
927
|
| `SUPERCOV_RUN_ID_FILE` | A file to write the recorded run's id to, for a script to query that run rather than `latest` |
|
|
928
928
|
| `SUPERCOV_KEEP_WORKSPACE` | Set to `1` to leave the instrumented copy of the project in `.supercov/workspaces/` after the run, to inspect what the command ran |
|
|
929
|
+
| `SUPERCOV_COMMAND_TIMEOUT_MS` | Stop the test command after this many milliseconds: what is still running is asked to stop, then made to five seconds later, what ran is recorded, and Supercov exits `124`. No deadline by default |
|
|
929
930
|
|
|
930
931
|
Examples:
|
|
931
932
|
|
|
@@ -941,6 +942,7 @@ SUPERCOV_TEST_KIND=e2e npx supercov -- npx playwright test
|
|
|
941
942
|
| `0` | The command or query succeeded |
|
|
942
943
|
| Wrapped command's code | The test command failed and Supercov preserved its status |
|
|
943
944
|
| `127`, `126` | The test command could not start: no such program, or one that may not be run, as a shell reports it |
|
|
945
|
+
| `124` | The test command ran past `SUPERCOV_COMMAND_TIMEOUT_MS` and was stopped |
|
|
944
946
|
| `1` | A valid measurement failed a policy you set, such as a coverage floor |
|
|
945
947
|
| `2` | Supercov could not complete the request, or the evidence cannot answer it |
|
|
946
948
|
|
package/docs/supported-suites.md
CHANGED
|
@@ -52,6 +52,7 @@ Supercov reports the level it actually observed. It does not guess.
|
|
|
52
52
|
| Vitest | Exact per test, with setup execution kept separate; Browser Mode included |
|
|
53
53
|
| Jest | Exact per test, including parameterized tests, with the user's own configuration, setup files and reporters kept |
|
|
54
54
|
| `node:test` | Exact per test |
|
|
55
|
+
| `bun:test` (Bun 1.2.17 and 1.4.4 verified) | Exact test-body coverage, including concurrent and parameterized tests; lifecycle hooks recorded as setup |
|
|
55
56
|
| Mocha | Exact per test and retry, `--parallel` included, with setup execution kept separate |
|
|
56
57
|
| AVA | Exact per test, concurrent tests included, in worker threads or child processes |
|
|
57
58
|
| node-tap 15 and newer | Exact per subtest, nested and parallel (`t.jobs`) subtests included |
|
|
@@ -61,6 +62,28 @@ Supercov reports the level it actually observed. It does not guess.
|
|
|
61
62
|
One command may launch several runners. Supercov combines their evidence into
|
|
62
63
|
one run and preserves runner identity wherever the runner exposes it.
|
|
63
64
|
|
|
65
|
+
### Bun
|
|
66
|
+
|
|
67
|
+
Run `supercov -- bun test` or the package script that launches it. Supercov
|
|
68
|
+
loads a native Bun adapter through `BUN_OPTIONS`, preserving existing Bun
|
|
69
|
+
options, configured preloads, and the project's files. Named ESM imports,
|
|
70
|
+
namespace imports, and CommonJS `require('bun:test')` are supported. Bun ignores
|
|
71
|
+
Node's preload settings, so the Node adapter alone cannot attribute its tests.
|
|
72
|
+
|
|
73
|
+
Synchronous `describe` boundaries form the test title. Parameterized rows use
|
|
74
|
+
the registration's title template and original row index in Supercov's queries;
|
|
75
|
+
Bun keeps its own formatted reporter titles. Async test bodies retain their
|
|
76
|
+
identity across `await`, including concurrent execution. Lifecycle hooks have
|
|
77
|
+
separate setup identities and do not credit their coverage to the test body.
|
|
78
|
+
|
|
79
|
+
Callback completion supplies the recorded outcome. If Bun interrupts a callback
|
|
80
|
+
before it completes, Supercov preserves its named coverage with an **unknown**
|
|
81
|
+
outcome. Bun's final runner verdict can additionally reflect a timeout, hook
|
|
82
|
+
failure, assertion-count check, or unhandled error outside that callback; those
|
|
83
|
+
verdicts are not inferred from callback completion. The measured command's exit
|
|
84
|
+
status remains Bun's own. Coverage from an API copied by a module mock before
|
|
85
|
+
adaptation may remain unattributed.
|
|
86
|
+
|
|
64
87
|
### Vitest Browser Mode
|
|
65
88
|
|
|
66
89
|
Vitest Browser Mode runs the test file in a real browser. Supercov measures it
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supercov",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.22.0",
|
|
4
4
|
"description": "Coverage, security and code quality for coding agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"coverage",
|
|
@@ -122,14 +122,14 @@
|
|
|
122
122
|
"test:windows-tls": "node scripts/windows-tls-test.mjs"
|
|
123
123
|
},
|
|
124
124
|
"optionalDependencies": {
|
|
125
|
-
"@supercov/cli-darwin-arm64": "4.
|
|
126
|
-
"@supercov/cli-darwin-x64": "4.
|
|
127
|
-
"@supercov/cli-linux-arm64-gnu": "4.
|
|
128
|
-
"@supercov/cli-linux-arm64-musl": "4.
|
|
129
|
-
"@supercov/cli-linux-x64-gnu": "4.
|
|
130
|
-
"@supercov/cli-linux-x64-musl": "4.
|
|
131
|
-
"@supercov/cli-win32-arm64": "4.
|
|
132
|
-
"@supercov/cli-win32-x64": "4.
|
|
125
|
+
"@supercov/cli-darwin-arm64": "4.22.0",
|
|
126
|
+
"@supercov/cli-darwin-x64": "4.22.0",
|
|
127
|
+
"@supercov/cli-linux-arm64-gnu": "4.22.0",
|
|
128
|
+
"@supercov/cli-linux-arm64-musl": "4.22.0",
|
|
129
|
+
"@supercov/cli-linux-x64-gnu": "4.22.0",
|
|
130
|
+
"@supercov/cli-linux-x64-musl": "4.22.0",
|
|
131
|
+
"@supercov/cli-win32-arm64": "4.22.0",
|
|
132
|
+
"@supercov/cli-win32-x64": "4.22.0"
|
|
133
133
|
},
|
|
134
134
|
"peerDependencies": {
|
|
135
135
|
"@playwright/test": ">=1.55.0",
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Bun does not load the Node --import preload. BUN_OPTIONS loads this before
|
|
2
|
+
// its test files and their configured preloads. Older Bun versions create a
|
|
3
|
+
// native API per file; newer versions share one. Adapt the factory and the
|
|
4
|
+
// existing API so both keep native test registration and execution semantics.
|
|
5
|
+
import * as runtime from "./runtime.mjs";
|
|
6
|
+
import { callerLocation, runnerExecutionScope, runnerTestId, writeRunnerEvidence } from "./runnerEvidence.mjs";
|
|
7
|
+
|
|
8
|
+
const adapted = new WeakSet();
|
|
9
|
+
const nativeJest = Bun.jest;
|
|
10
|
+
const attempts = new Map();
|
|
11
|
+
function writeEvidence(identity, status, scope, directory, phases = [], source) {
|
|
12
|
+
return writeRunnerEvidence(identity, status, scope, directory, phases, source, true);
|
|
13
|
+
}
|
|
14
|
+
const previousPhaseObserver = globalThis.__SUPERCOV_ASSERTION_PHASE_STARTED__;
|
|
15
|
+
globalThis.__SUPERCOV_ASSERTION_PHASE_STARTED__ = (scope, phase) => {
|
|
16
|
+
previousPhaseObserver?.(scope, phase);
|
|
17
|
+
const attempt = attempts.get(scope.attemptId);
|
|
18
|
+
if (attempt) {
|
|
19
|
+
attempt.phases.push(phase);
|
|
20
|
+
writeEvidence(attempt.identity, "unknown", scope, undefined, attempt.phases);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function adapt(api) {
|
|
24
|
+
if (adapted.has(api)) return api;
|
|
25
|
+
adapted.add(api);
|
|
26
|
+
const originals = { ...api };
|
|
27
|
+
let suites = [];
|
|
28
|
+
const registrations = new Map();
|
|
29
|
+
const evidenceDirectory = process.env.SUPERCOV_EVIDENCE_DIR;
|
|
30
|
+
|
|
31
|
+
function callbackIndex(args) {
|
|
32
|
+
return args.findLastIndex(value => typeof value === "function");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function wrap(original, kind, flags = {}, row = {}) {
|
|
36
|
+
const members = new Map();
|
|
37
|
+
function supercovBunRegistration(...args) {
|
|
38
|
+
const index = callbackIndex(args);
|
|
39
|
+
const callback = index < 0 ? undefined : args[index];
|
|
40
|
+
const name = row.name ?? (typeof args[0] === "string" ? args[0] : callback?.name || "anonymous test");
|
|
41
|
+
const location = row.location ?? callerLocation(supercovBunRegistration);
|
|
42
|
+
if (kind === "suite") {
|
|
43
|
+
if (!callback) return Reflect.apply(original, this, args);
|
|
44
|
+
const next = [...args];
|
|
45
|
+
next[index] = function () {
|
|
46
|
+
const values = [...arguments];
|
|
47
|
+
const parent = suites;
|
|
48
|
+
suites = [...parent, { name, flags }];
|
|
49
|
+
try { return Reflect.apply(callback, this, values); }
|
|
50
|
+
finally { suites = parent; }
|
|
51
|
+
};
|
|
52
|
+
return Reflect.apply(original, this, next);
|
|
53
|
+
}
|
|
54
|
+
const identity = {
|
|
55
|
+
runner: "bun:test",
|
|
56
|
+
name: kind === "test" ? [...suites.map(suite => suite.name), name].join(" > ") : `[${kind}] ${name}`,
|
|
57
|
+
...location,
|
|
58
|
+
...(location.line === undefined ? {} : { column: 1 }),
|
|
59
|
+
...(kind === "test" ? {} : { role: "setup" }),
|
|
60
|
+
};
|
|
61
|
+
const key = runnerTestId(identity);
|
|
62
|
+
identity.registrationOrdinal = registrations.get(key) ?? 0;
|
|
63
|
+
registrations.set(key, identity.registrationOrdinal + 1);
|
|
64
|
+
const effective = {};
|
|
65
|
+
for (const active of [...suites.map(suite => suite.flags), flags]) {
|
|
66
|
+
for (const [key, value] of Object.entries(active)) effective[key] ||= value;
|
|
67
|
+
}
|
|
68
|
+
if (!callback) {
|
|
69
|
+
const result = Reflect.apply(original, this, args);
|
|
70
|
+
writeEvidence(identity, effective.todo ? "todo" : "skipped", runnerExecutionScope(identity), evidenceDirectory);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
let attempt = 0;
|
|
74
|
+
const next = [...args];
|
|
75
|
+
next[index] = function () {
|
|
76
|
+
const values = [...arguments];
|
|
77
|
+
const current = { ...identity, retry: attempt++ };
|
|
78
|
+
const scope = runnerExecutionScope(current);
|
|
79
|
+
// Bun may terminate a timed-out callback without running its
|
|
80
|
+
// finally or Node exit hooks. Persist identity before execution,
|
|
81
|
+
// and use the runtime's per-turn transport instead of buffering
|
|
82
|
+
// the whole attempt until callback completion.
|
|
83
|
+
attempts.set(scope.attemptId, { identity: current, phases: [] });
|
|
84
|
+
writeEvidence(current, "unknown", scope, evidenceDirectory);
|
|
85
|
+
let finished = false;
|
|
86
|
+
function finish(failed = false) {
|
|
87
|
+
if (finished) return;
|
|
88
|
+
finished = true;
|
|
89
|
+
attempts.delete(scope.attemptId);
|
|
90
|
+
const status = effective.todo ? "todo" : failed === Boolean(effective.failing) ? "passed" : "failed";
|
|
91
|
+
writeEvidence(current, status, scope, evidenceDirectory, runtime.takeNodeAssertionPhases(scope));
|
|
92
|
+
}
|
|
93
|
+
const actual = [...values];
|
|
94
|
+
const bound = row.bound ?? 0;
|
|
95
|
+
if (callback.length > bound && typeof actual[bound] === "function") {
|
|
96
|
+
const done = actual[bound];
|
|
97
|
+
actual[bound] = error => { finish(error != null); return done(error); };
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const result = runtime.withCoverageCarrier({ version: 1, scope }, () => Reflect.apply(callback, this, actual));
|
|
101
|
+
if (result && typeof result.then === "function") {
|
|
102
|
+
return Promise.resolve(result).then(value => { finish(); return value; }, error => { finish(true); throw error; });
|
|
103
|
+
}
|
|
104
|
+
if (callback.length <= bound) finish();
|
|
105
|
+
return result;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
finish(true);
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
Object.defineProperty(next[index], "length", { value: callback.length });
|
|
112
|
+
const result = Reflect.apply(original, this, next);
|
|
113
|
+
if (effective.skip || effective.todo) {
|
|
114
|
+
writeEvidence(identity, effective.todo ? "todo" : "skipped", runnerExecutionScope(identity), evidenceDirectory);
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
return new Proxy(supercovBunRegistration, {
|
|
119
|
+
get(target, property) {
|
|
120
|
+
if (["call", "apply", "bind"].includes(property)) return Reflect.get(target, property);
|
|
121
|
+
|
|
122
|
+
if (members.has(property)) return members.get(property);
|
|
123
|
+
const member = Reflect.get(original, property, original);
|
|
124
|
+
if (typeof member !== "function") return member;
|
|
125
|
+
if (property === "each") {
|
|
126
|
+
const each = table => {
|
|
127
|
+
const nativeEach = Reflect.apply(member, original, [table]);
|
|
128
|
+
if (!Array.isArray(table) || table.length === 0) return nativeEach;
|
|
129
|
+
return function supercovBunEach(...args) {
|
|
130
|
+
const location = callerLocation(supercovBunEach);
|
|
131
|
+
for (const [index, value] of table.entries()) {
|
|
132
|
+
const bound = Array.isArray(value) ? value.length : 1;
|
|
133
|
+
const next = [...args];
|
|
134
|
+
// Keep Bun's own formatting and binding. Only %# needs
|
|
135
|
+
// the original index when registering one row at a time.
|
|
136
|
+
let consumed = 0;
|
|
137
|
+
if (typeof next[0] === "string") next[0] = next[0].replace(/%([sidfjop#%])/g, (token, format) => {
|
|
138
|
+
if (consumed >= bound) return token;
|
|
139
|
+
if (format === "#") return String(index);
|
|
140
|
+
if (format !== "%") consumed++;
|
|
141
|
+
return token;
|
|
142
|
+
});
|
|
143
|
+
const register = Reflect.apply(member, original, [[value]]);
|
|
144
|
+
Reflect.apply(wrap(register, kind, flags, {
|
|
145
|
+
location, bound,
|
|
146
|
+
name: `${args[0]} [row ${index + 1}]`,
|
|
147
|
+
}), this, next);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
members.set(property, each);
|
|
152
|
+
return each;
|
|
153
|
+
}
|
|
154
|
+
if (["skip", "todo", "only", "concurrent", "serial", "failing"].includes(property)) {
|
|
155
|
+
const wrapped = wrap(member, kind, { ...flags, [property]: true });
|
|
156
|
+
members.set(property, wrapped);
|
|
157
|
+
return wrapped;
|
|
158
|
+
}
|
|
159
|
+
if (["if", "skipIf", "todoIf", "failingIf", "concurrentIf", "serialIf"].includes(property)) {
|
|
160
|
+
return (...values) => wrap(Reflect.apply(member, original, values), kind, {
|
|
161
|
+
...flags,
|
|
162
|
+
[property === "if" ? "skip" : property.slice(0, -2)]: flags[property === "if" ? "skip" : property.slice(0, -2)] || (property === "if" ? !values[0] : Boolean(values[0])),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return member.bind(original);
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
api.test = api.it = wrap(originals.test, "test");
|
|
171
|
+
api.describe = wrap(originals.describe, "suite");
|
|
172
|
+
for (const [name, kind, flags] of [
|
|
173
|
+
["xtest", "test", { skip: true }], ["xit", "test", { skip: true }],
|
|
174
|
+
["xdescribe", "suite", { skip: true }],
|
|
175
|
+
["beforeAll", "beforeAll", {}], ["afterAll", "afterAll", {}],
|
|
176
|
+
["beforeEach", "beforeEach", {}], ["afterEach", "afterEach", {}],
|
|
177
|
+
]) {
|
|
178
|
+
if (typeof originals[name] === "function") api[name] = wrap(originals[name], kind, flags);
|
|
179
|
+
}
|
|
180
|
+
return api;
|
|
181
|
+
}
|
|
182
|
+
if (typeof nativeJest === "function") {
|
|
183
|
+
Bun.jest = function (...args) { return adapt(Reflect.apply(nativeJest, this, args)); };
|
|
184
|
+
adapt(Reflect.apply(nativeJest, Bun, [import.meta.path]));
|
|
185
|
+
}
|
|
@@ -139,7 +139,7 @@ function inferredProvenance({ runner, file, project, explicitKind, }) {
|
|
|
139
139
|
source: "path",
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
|
-
const defaultKind = runner === "vitest" || runner === "jest" || runner === "node:test" || runner === "mocha" || runner === "ava" || runner === "tap"
|
|
142
|
+
const defaultKind = runner === "vitest" || runner === "jest" || runner === "node:test" || runner === "bun:test" || runner === "mocha" || runner === "ava" || runner === "tap"
|
|
143
143
|
? "unit"
|
|
144
144
|
: "unknown";
|
|
145
145
|
return {
|
|
@@ -944,10 +944,40 @@ if (supportDirectory) {
|
|
|
944
944
|
};
|
|
945
945
|
syncBuiltinESMExports();
|
|
946
946
|
}
|
|
947
|
+
// A file a test asked for by its path that Node did not find in the copy:
|
|
948
|
+
// the copy leaves out build output, and a test that loads `../dist/x.js` an
|
|
949
|
+
// earlier build made fails as a missing module. Noted where
|
|
950
|
+
// SUPERCOV_MISSING_LOADS names, here for `require` and in the loader for
|
|
951
|
+
// `import`, so the run can say which file the copy lacks and why.
|
|
952
|
+
const missingFile = process.env.SUPERCOV_MISSING_LOADS;
|
|
953
|
+
if (missingFile) {
|
|
954
|
+
const copyRoot = process.env.SUPERCOV_PROJECT_ROOT;
|
|
955
|
+
const resolveFilename = Module._resolveFilename;
|
|
956
|
+
Module._resolveFilename = function supercovNotedResolve(request, parent, ...rest) {
|
|
957
|
+
try {
|
|
958
|
+
return resolveFilename.call(this, request, parent, ...rest);
|
|
959
|
+
}
|
|
960
|
+
catch (error) {
|
|
961
|
+
if (error?.code === "MODULE_NOT_FOUND" && copyRoot && /^(?:\.{1,2}[\\/]|[\\/])/.test(request)) {
|
|
962
|
+
try {
|
|
963
|
+
const file = resolve(parent?.filename ? dirname(parent.filename) : process.cwd(), request);
|
|
964
|
+
const inside = file.startsWith(copyRoot + sep) ? file.slice(copyRoot.length + 1) : undefined;
|
|
965
|
+
if (inside && !inside.startsWith(`.supercov${sep}`) && !inside.split(sep).includes("node_modules"))
|
|
966
|
+
fs.appendFileSync(missingFile, `${JSON.stringify({ missing: file })}\n`);
|
|
967
|
+
}
|
|
968
|
+
catch {
|
|
969
|
+
// Not noted: the run says nothing of it, as before.
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
throw error;
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
}
|
|
947
976
|
register(new URL("./resolve-loader.mjs", import.meta.url), {
|
|
948
977
|
data: {
|
|
949
978
|
typescriptRequireHook: typeof typescriptHandler === "function" && typescriptHandler.name !== "loadTS",
|
|
950
979
|
supportFile,
|
|
980
|
+
missingFile,
|
|
951
981
|
},
|
|
952
982
|
});
|
|
953
983
|
if (process.env.SUPERCOV_DEBUG === "1") {
|
|
@@ -58,9 +58,27 @@ function noteLoaded(url) {
|
|
|
58
58
|
// Not recorded: the file counts for every test, as before.
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
// Where this process notes a file a test asked for by its path and Node did
|
|
62
|
+
// not find, in the copy: a file the copy left out, build output above all,
|
|
63
|
+
// fails a test as a missing module, and Supercov can say why (`register.mjs`
|
|
64
|
+
// names it, and does the same for `require`).
|
|
65
|
+
let missingFile;
|
|
66
|
+
function noteMissing(specifier, parentURL) {
|
|
67
|
+
if (!missingFile || !/^(?:\.{1,2}\/|\/|file:)/.test(specifier))
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
const file = fileURLToPath(new URL(specifier, parentURL ?? pathToFileURL(`${process.cwd()}/`)));
|
|
71
|
+
if (projectFile(file))
|
|
72
|
+
appendFileSync(missingFile, `${JSON.stringify({ missing: file })}\n`);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Not noted: the run says nothing of it, as before.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
61
78
|
export function initialize(data) {
|
|
62
79
|
typescriptRequireHook = data?.typescriptRequireHook === true;
|
|
63
80
|
supportFile = typeof data?.supportFile === "string" ? data.supportFile : undefined;
|
|
81
|
+
missingFile = typeof data?.missingFile === "string" ? data.missingFile : undefined;
|
|
64
82
|
}
|
|
65
83
|
const packageTypes = new Map();
|
|
66
84
|
function packageType(directory) {
|
|
@@ -141,7 +159,14 @@ export async function resolve(specifier, context, nextResolve) {
|
|
|
141
159
|
}
|
|
142
160
|
return nextResolve(REPLACEMENT, context);
|
|
143
161
|
}
|
|
144
|
-
|
|
162
|
+
try {
|
|
163
|
+
return throughRequireHook(await nextResolve(specifier, context));
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (error?.code === "ERR_MODULE_NOT_FOUND")
|
|
167
|
+
noteMissing(specifier, context.parentURL);
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
145
170
|
}
|
|
146
171
|
|
|
147
172
|
// Node's own test coverage filters files by `--test-coverage-include` and
|
|
@@ -2,7 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
-
import { appendEvidenceRecord } from "./atomic.mjs";
|
|
5
|
+
import { appendEvidenceRecord, atomicWriteFileSync } from "./atomic.mjs";
|
|
6
6
|
import { inferTestProvenance, testTags } from "./provenance.mjs";
|
|
7
7
|
import { serverEvidencePath } from "./transport.mjs";
|
|
8
8
|
|
|
@@ -105,7 +105,7 @@ export function readScopedServerEvidence(scope, evidencePath = serverEvidencePat
|
|
|
105
105
|
return [];
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
export function writeRunnerEvidence(identity, status, scope, evidenceDirectoryOverride, phases = [], serverEvidenceSource) {
|
|
108
|
+
export function writeRunnerEvidence(identity, status, scope, evidenceDirectoryOverride, phases = [], serverEvidenceSource, snapshot = false) {
|
|
109
109
|
const evidenceDirectory = evidenceDirectoryOverride ?? process.env["SUPERCOV_EVIDENCE_DIR"];
|
|
110
110
|
if (!evidenceDirectory)
|
|
111
111
|
return;
|
|
@@ -132,7 +132,11 @@ export function writeRunnerEvidence(identity, status, scope, evidenceDirectoryOv
|
|
|
132
132
|
? readScopedServerEvidence(scope, serverEvidenceSource)
|
|
133
133
|
: readScopedServerEvidence(scope),
|
|
134
134
|
};
|
|
135
|
-
|
|
135
|
+
if (snapshot) {
|
|
136
|
+
atomicWriteFileSync(resolve(process.cwd(), evidenceDirectory, `attempt-${scope.attemptId}`, "mcdc.json"), JSON.stringify(payload));
|
|
137
|
+
} else {
|
|
138
|
+
appendEvidenceRecord(resolve(process.cwd(), evidenceDirectory), identity.runner.replace(/[^A-Za-z0-9_-]/g, "_"), payload);
|
|
139
|
+
}
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
// A failing test's stack as its author would have seen it, for the runners
|
|
@@ -1251,6 +1251,8 @@ function withNodeAssertionPhase(operation, source, callback) {
|
|
|
1251
1251
|
});
|
|
1252
1252
|
attempt.phases.push(phase);
|
|
1253
1253
|
attempt.phaseIds.add(phase.id);
|
|
1254
|
+
const phaseObserver = runtimeGlobal.__SUPERCOV_ASSERTION_PHASE_STARTED__;
|
|
1255
|
+
if (phaseObserver) phaseObserver(scope, phase);
|
|
1254
1256
|
try {
|
|
1255
1257
|
const result = withStackRoom(() => withCoverageCarrier({ version: 1, scope, phaseId: phase.id }, callback));
|
|
1256
1258
|
if (result && typeof result.then === "function")
|
|
@@ -1326,6 +1328,8 @@ function openNodeAssertionPhase(operation, source) {
|
|
|
1326
1328
|
});
|
|
1327
1329
|
attempt.phases.push(phase);
|
|
1328
1330
|
attempt.phaseIds.add(phase.id);
|
|
1331
|
+
const phaseObserver = runtimeGlobal.__SUPERCOV_ASSERTION_PHASE_STARTED__;
|
|
1332
|
+
if (phaseObserver) phaseObserver(scope, phase);
|
|
1329
1333
|
carrier = { version: 1, scope, phaseId: phase.id };
|
|
1330
1334
|
finish = (error) => finishAssertionPhase(phase, error);
|
|
1331
1335
|
}
|