supercov 0.0.12 → 0.0.14
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 +14 -13
- package/docs/performance.md +20 -4
- package/docs/workspace-isolation.md +1 -1
- package/package.json +7 -7
- package/runtime/javascript/atomic.js +21 -1
- package/runtime/javascript/launchSupervisor.js +51 -1
- package/runtime/javascript/playwright.js +15 -10
- package/runtime/javascript/playwrightReporter.js +17 -6
- package/runtime/javascript/register.mjs +30 -7
- package/runtime/javascript/runtime.js +46 -0
package/README.md
CHANGED
|
@@ -129,10 +129,11 @@ npx supercov diff <older-run> <newer-run>
|
|
|
129
129
|
npx supercov merge <first-run-id> <second-run-id>
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
`supercov runs`
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
`supercov runs` always includes coverage percentages. Supercov derives a
|
|
133
|
+
missing disposable query view from the run's immutable evidence before printing
|
|
134
|
+
that row; whether the view was already available is never exposed in the CLI.
|
|
135
|
+
`supercov runs <run-id>` prints the same summary as
|
|
136
|
+
`supercov runs <run-id> coverage`.
|
|
136
137
|
|
|
137
138
|
Coverage queries use `--filter all` by default, matching conventional coverage
|
|
138
139
|
tools: every executed attempt contributes, including attempts that later fail.
|
|
@@ -195,7 +196,7 @@ producing a plausible but invalid aggregate.
|
|
|
195
196
|
For a JavaScript or TypeScript project, the CLI:
|
|
196
197
|
|
|
197
198
|
1. refreshes a stable isolated source namespace under
|
|
198
|
-
|
|
199
|
+
`.supercov/cache/workspace/<project>/`, links the existing
|
|
199
200
|
dependency tree, and creates generated runner configuration and build output
|
|
200
201
|
only there; file data uses copy-on-write reflinks where the filesystem
|
|
201
202
|
supports them, and falls back to copying where it does not; the stable path
|
|
@@ -228,7 +229,7 @@ For a JavaScript or TypeScript project, the CLI:
|
|
|
228
229
|
rebuilt afterward.
|
|
229
230
|
|
|
230
231
|
Only the Supercov-owned `.supercov/` run store and marker-protected
|
|
231
|
-
|
|
232
|
+
`.supercov/cache/workspace/` cache are modified in the user's checkout. A user-created
|
|
232
233
|
`supercov/` directory without Supercov's ownership marker is never treated as
|
|
233
234
|
storage. A per-project lock rejects overlapping runs before either can build. Run state is durably written
|
|
234
235
|
through preparing/building/testing/publishing phases; SIGINT, SIGTERM,
|
|
@@ -244,17 +245,17 @@ state writes use sibling-temp files, fsync, and atomic rename; lock acquisition
|
|
|
244
245
|
uses exclusive creation and fsync. Published `run.json` is the durable terminal
|
|
245
246
|
record, so terminal work state is not retained.
|
|
246
247
|
|
|
247
|
-
Retention is deterministic because
|
|
248
|
+
Retention is deterministic because run records store their UTC start time:
|
|
248
249
|
|
|
249
250
|
```sh
|
|
250
|
-
npx supercov
|
|
251
|
-
npx supercov
|
|
252
|
-
npx supercov clean --keep 20 #
|
|
251
|
+
npx supercov clean # removes all stored runs and the shared build cache
|
|
252
|
+
npx supercov clean --dry-run # previews the default full cleanup
|
|
253
|
+
npx supercov clean --keep 20 # explicitly retains the 20 newest runs
|
|
253
254
|
```
|
|
254
255
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
Cleanup never runs automatically. `clean` removes explicit history beyond the
|
|
257
|
+
requested retention, orphan/terminal transient data, and the shared build
|
|
258
|
+
cache. It acquires the same lock
|
|
258
259
|
as a coverage run, refuse to race an active run, and never touch files outside
|
|
259
260
|
`.supercov/`.
|
|
260
261
|
|
package/docs/performance.md
CHANGED
|
@@ -43,6 +43,20 @@ reported separately from Supercov's recorded phases.
|
|
|
43
43
|
|
|
44
44
|
## Reference measurement, not a guarantee
|
|
45
45
|
|
|
46
|
+
On 2026-08-26, the 30-test Essential SEO Playwright suite running across seven
|
|
47
|
+
cycle-restored VMs produced a 43.02 s uncovered control and two valid warm
|
|
48
|
+
Supercov measurements of 46.30 s and 40.20 s. The slower covered sample is
|
|
49
|
+
1.076x the control and therefore remains below the current 1.1x realistic-suite
|
|
50
|
+
target; the faster result illustrates why multiple alternating pairs are still
|
|
51
|
+
required for a statistically stable benchmark. The 40.20 s run spent 38.63 s
|
|
52
|
+
inside the unchanged test command and 1.04 s total on initialization, workspace
|
|
53
|
+
refresh, adapter restoration and evidence publication. It attributed 6,727
|
|
54
|
+
server records across 30 remote launches with zero corrupt evidence records.
|
|
55
|
+
|
|
56
|
+
VM-image creation is not included in these warm comparisons. A changed engine
|
|
57
|
+
fingerprint intentionally caused a one-time 119 s rebake before the first
|
|
58
|
+
post-change run; comparing that cold run with a warm control would be invalid.
|
|
59
|
+
|
|
46
60
|
On 2026-08-24, the 29-test Essential SEO offline suite on the development Mac
|
|
47
61
|
produced this warm pair:
|
|
48
62
|
|
|
@@ -73,8 +87,10 @@ Before raw-evidence-only storage, the reference run retained 4.5 MB of reports
|
|
|
73
87
|
and 1.7 MB across 178 loose evidence files. Its canonical compressed JSON was
|
|
74
88
|
0.9 MB. The execution evidence alone packed to about 121 KiB; current archives
|
|
75
89
|
also embed the exact denominator manifest and are the sole coverage artifact.
|
|
76
|
-
Every CLI query derives its view from the archive
|
|
77
|
-
|
|
90
|
+
Every CLI query derives its view from the archive. The first query builds a
|
|
91
|
+
disposable integrity-bound binary index; later queries reuse it while the run
|
|
92
|
+
identity remains valid. The index is an implementation detail and can always be
|
|
93
|
+
deleted and reconstructed.
|
|
78
94
|
These numbers are application- and filesystem-specific optimization baselines.
|
|
79
95
|
An exact matching Vite build is also reused across runs, removing the measured
|
|
80
96
|
4.87-second repeated build; any source/configuration/toolchain-key change falls
|
|
@@ -88,8 +104,8 @@ phase and 40.49 seconds total: 0.07 seconds initialization, 0.36 seconds
|
|
|
88
104
|
workspace refresh, 0.05 seconds adapter setup, 39.84 seconds in the unchanged
|
|
89
105
|
test command, and 0.12 seconds evidence validation/archive publication. Fresh
|
|
90
106
|
process queries for summary, files, and gaps each took 0.16–0.20 seconds on
|
|
91
|
-
this run
|
|
92
|
-
naturally variable part of the total.
|
|
107
|
+
this run before the reusable query index was introduced. Test execution is
|
|
108
|
+
still the dominant and naturally variable part of the total.
|
|
93
109
|
|
|
94
110
|
## Isolation strategy trade-offs
|
|
95
111
|
|
|
@@ -16,7 +16,7 @@ is below the project's `.supercov/` directory:
|
|
|
16
16
|
| `work/<run>/state.json` | In-flight lifecycle record; removed after atomic run publication. |
|
|
17
17
|
| `work/<run>/run-publication/` | Incomplete run staging; atomically renamed or removed on recovery. |
|
|
18
18
|
| `evidence/<run>/` | Loose in-flight evidence; packed and removed after publication. |
|
|
19
|
-
| `runs/<run>/` | Immutable `evidence.raw.gz` (manifest plus raw execution evidence) and `run.json`; retained until explicit
|
|
19
|
+
| `runs/<run>/` | Immutable `evidence.raw.gz` (manifest plus raw execution evidence) and `run.json`; retained until explicit `clean`. Derived query views are cached only after their first query. |
|
|
20
20
|
| `cache/instrumented-workspace/<project>/` | Stable physical fallback and provider snapshot cache. |
|
|
21
21
|
| `cache/instrumented-workspace/.<project>.staging-*` | Unpublished cache transaction; removed on error or recovery. |
|
|
22
22
|
| `cache/instrumented-workspace/.<project>.previous-*` | Last complete cache generation during publication; restored or removed on recovery. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supercov",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "Zero-edit, runner-aware coverage completeness for JavaScript test suites",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -63,12 +63,12 @@
|
|
|
63
63
|
"prepublishOnly": "npm run release:check"
|
|
64
64
|
},
|
|
65
65
|
"optionalDependencies": {
|
|
66
|
-
"@supercov/cli-darwin-arm64": "0.0.
|
|
67
|
-
"@supercov/cli-darwin-x64": "0.0.
|
|
68
|
-
"@supercov/cli-linux-arm64-gnu": "0.0.
|
|
69
|
-
"@supercov/cli-linux-arm64-musl": "0.0.
|
|
70
|
-
"@supercov/cli-linux-x64-gnu": "0.0.
|
|
71
|
-
"@supercov/cli-linux-x64-musl": "0.0.
|
|
66
|
+
"@supercov/cli-darwin-arm64": "0.0.14",
|
|
67
|
+
"@supercov/cli-darwin-x64": "0.0.14",
|
|
68
|
+
"@supercov/cli-linux-arm64-gnu": "0.0.14",
|
|
69
|
+
"@supercov/cli-linux-arm64-musl": "0.0.14",
|
|
70
|
+
"@supercov/cli-linux-x64-gnu": "0.0.14",
|
|
71
|
+
"@supercov/cli-linux-x64-musl": "0.0.14"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
74
|
"@playwright/test": ">=1.55.0",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, openSync, closeSync, fsyncSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, openSync, closeSync, fsyncSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
function fsyncDirectory(path) {
|
|
@@ -38,6 +38,26 @@ export function atomicWriteFileSync(path, data, options) {
|
|
|
38
38
|
rmSync(temporary, { force: true });
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
/** Append one recoverable JSONL record and make the completed line durable. */
|
|
42
|
+
export function appendJsonLineDurableSync(path, data) {
|
|
43
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
44
|
+
const existed = existsSync(path);
|
|
45
|
+
const descriptor = openSync(path, "a", 0o600);
|
|
46
|
+
try {
|
|
47
|
+
writeFileSync(descriptor, data.endsWith("\n") ? data : `${data}\n`);
|
|
48
|
+
fsyncSync(descriptor);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
closeSync(descriptor);
|
|
52
|
+
}
|
|
53
|
+
if (!existed)
|
|
54
|
+
fsyncDirectory(dirname(path));
|
|
55
|
+
}
|
|
56
|
+
/** Append a complete local record; process exit closes it before publication. */
|
|
57
|
+
export function appendJsonLineSync(path, data) {
|
|
58
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
59
|
+
appendFileSync(path, data.endsWith("\n") ? data : `${data}\n`, { mode: 0o600 });
|
|
60
|
+
}
|
|
41
61
|
/** Atomically publish a fully prepared file or directory and persist its entry. */
|
|
42
62
|
export function atomicRenameSync(source, destination) {
|
|
43
63
|
mkdirSync(dirname(destination), { recursive: true });
|
|
@@ -16,6 +16,7 @@ const patchedBuilders = new WeakSet();
|
|
|
16
16
|
const exportedValues = new WeakSet();
|
|
17
17
|
const capabilityProxies = new WeakMap();
|
|
18
18
|
const importedCapabilityProxies = new WeakMap();
|
|
19
|
+
const importedMemberProxies = new WeakMap();
|
|
19
20
|
let installed = false;
|
|
20
21
|
let remoteLaunchSequence = 0;
|
|
21
22
|
function executionLogPath(path) {
|
|
@@ -189,6 +190,7 @@ export function guestCoverageEnvironment(mapping, coverageEnvironment = process.
|
|
|
189
190
|
...translated,
|
|
190
191
|
SUPERCOV_PROJECT_ROOT: mapping.guestRoot,
|
|
191
192
|
SUPERCOV_CJS_INTERCEPT: "1",
|
|
193
|
+
SUPERCOV_DURABLE_EVIDENCE_EACH_TEST: "1",
|
|
192
194
|
NODE_OPTIONS: appendNodeImport(existingEnvironment.NODE_OPTIONS, registerUrl),
|
|
193
195
|
};
|
|
194
196
|
}
|
|
@@ -227,6 +229,43 @@ function wrapResult(value, mapping) {
|
|
|
227
229
|
return value.then((result) => wrapCapabilityObject(result, mapping));
|
|
228
230
|
return wrapCapabilityObject(value, mapping);
|
|
229
231
|
}
|
|
232
|
+
|
|
233
|
+
// A Proxy `get` trap must return the exact value of a non-configurable,
|
|
234
|
+
// non-writable own data property (and `undefined` for a non-configurable
|
|
235
|
+
// accessor without a getter). Constructors commonly expose `prototype` this
|
|
236
|
+
// way. Returning another capability proxy is a TypeError before user code can
|
|
237
|
+
// run, as seen with PrismaSessionStorage during an Essential Apps VM bake.
|
|
238
|
+
function fixedProxyValue(target, property) {
|
|
239
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
|
|
240
|
+
if (!descriptor || descriptor.configurable)
|
|
241
|
+
return { fixed: false };
|
|
242
|
+
if ("value" in descriptor && !descriptor.writable)
|
|
243
|
+
return { fixed: true, value: descriptor.value };
|
|
244
|
+
if (!("value" in descriptor) && descriptor.get === undefined)
|
|
245
|
+
return { fixed: true, value: undefined };
|
|
246
|
+
return { fixed: false };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function wrapImportedMember(member, receiver, invoke) {
|
|
250
|
+
let members = importedMemberProxies.get(receiver);
|
|
251
|
+
if (!members) {
|
|
252
|
+
members = new WeakMap();
|
|
253
|
+
importedMemberProxies.set(receiver, members);
|
|
254
|
+
}
|
|
255
|
+
const cached = members.get(member);
|
|
256
|
+
if (cached)
|
|
257
|
+
return cached;
|
|
258
|
+
const proxy = new Proxy(member, {
|
|
259
|
+
apply(target, _thisArgument, args) {
|
|
260
|
+
return invoke(target, receiver, args);
|
|
261
|
+
},
|
|
262
|
+
construct(target, args, newTarget) {
|
|
263
|
+
return wrapImportedCapability(Reflect.construct(target, args, newTarget));
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
members.set(member, proxy);
|
|
267
|
+
return proxy;
|
|
268
|
+
}
|
|
230
269
|
/**
|
|
231
270
|
* A remote SDK can hide its first executable launch inside a configuration
|
|
232
271
|
* callback (for example an image warmup hook). Decorate callbacks in ordinary
|
|
@@ -287,6 +326,9 @@ export function wrapCapabilityObject(value, mapping) {
|
|
|
287
326
|
return cached;
|
|
288
327
|
const proxy = new Proxy(object, {
|
|
289
328
|
get(target, property) {
|
|
329
|
+
const fixed = fixedProxyValue(target, property);
|
|
330
|
+
if (fixed.fixed)
|
|
331
|
+
return fixed.value;
|
|
290
332
|
// Use the real target as the receiver so SDK getters backed by private
|
|
291
333
|
// fields keep their brand check. Method calls are likewise bound below.
|
|
292
334
|
const member = Reflect.get(target, property, target);
|
|
@@ -362,10 +404,13 @@ export function wrapImportedCapability(value) {
|
|
|
362
404
|
};
|
|
363
405
|
const proxy = new Proxy(object, {
|
|
364
406
|
get(target, property) {
|
|
407
|
+
const fixed = fixedProxyValue(target, property);
|
|
408
|
+
if (fixed.fixed)
|
|
409
|
+
return fixed.value;
|
|
365
410
|
const member = Reflect.get(target, property, target);
|
|
366
411
|
if (typeof member !== "function")
|
|
367
412
|
return wrapImportedCapability(member);
|
|
368
|
-
return (
|
|
413
|
+
return wrapImportedMember(member, target, invoke);
|
|
369
414
|
},
|
|
370
415
|
...(typeof value === "function"
|
|
371
416
|
? {
|
|
@@ -512,6 +557,11 @@ export function installLaunchSupervisor() {
|
|
|
512
557
|
const originalLoad = moduleLoader._load;
|
|
513
558
|
moduleLoader._load = function supercovCapabilityLoad(request, parent, isMain) {
|
|
514
559
|
const exports = originalLoad.call(this, request, parent, isMain);
|
|
560
|
+
// A user's test command may itself be implemented by an installed
|
|
561
|
+
// package. That package can import a VM/container/process SDK without
|
|
562
|
+
// any project-owned module ever crossing the SDK boundary. Inspect all
|
|
563
|
+
// newly loaded export graphs (deduplicated by `exportedValues`) so
|
|
564
|
+
// capability discovery remains provider- and runner-neutral.
|
|
515
565
|
inspectExports(exports);
|
|
516
566
|
return exports;
|
|
517
567
|
};
|
|
@@ -12,10 +12,11 @@ import { mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
|
12
12
|
import http from "node:http";
|
|
13
13
|
import https from "node:https";
|
|
14
14
|
import { syncBuiltinESMExports } from "node:module";
|
|
15
|
-
import {
|
|
15
|
+
import { relative, resolve, sep } from "node:path";
|
|
16
16
|
import * as standardPlaywright from "@playwright/test";
|
|
17
|
+
import * as coverageRuntime from "./runtime.js";
|
|
17
18
|
import { inferTestProvenance } from "./provenance.js";
|
|
18
|
-
import {
|
|
19
|
+
import { appendJsonLineDurableSync, appendJsonLineSync } from "./atomic.js";
|
|
19
20
|
import { COVERAGE_PHASE_HEADER, COVERAGE_PHASE_COOKIE, COVERAGE_SCOPE_COOKIE, COVERAGE_SCOPE_HEADER, COVERAGE_CARRIER_ENV, encodeCoverageCarrier, encodeCoverageScope, serverEvidenceDirectory, serverEvidencePath, } from "./transport.js";
|
|
20
21
|
export * from "@playwright/test";
|
|
21
22
|
const generatedTargetModule = "__SUPERCOV_PLAYWRIGHT_MODULE__";
|
|
@@ -32,6 +33,8 @@ const adapter = (targetModule === "@playwright/test"
|
|
|
32
33
|
const base = (adapter[targetTestExport] ?? adapter.test);
|
|
33
34
|
const baseExpect = adapter.expect;
|
|
34
35
|
const GENERATED_EVIDENCE_DIRECTORY = "__SUPERCOV_EVIDENCE_DIRECTORY__";
|
|
36
|
+
const evidenceWriterIdentity = () => (process.env.SUPERCOV_EXECUTION_LOG_SHARD ?? `pid-${process.pid}`)
|
|
37
|
+
.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
35
38
|
const GENERATED_RUN_ID = "__SUPERCOV_RUN_ID__";
|
|
36
39
|
const PHASE_STORAGE_KEY = "__supercov_phase";
|
|
37
40
|
const ACTION_METHODS = new Set([
|
|
@@ -329,6 +332,11 @@ class CoveragePhaseController {
|
|
|
329
332
|
},
|
|
330
333
|
});
|
|
331
334
|
this.proxyCache.set(target, proxy);
|
|
335
|
+
// Playwright's built-in `page` fixture is created through the wrapped
|
|
336
|
+
// worker-scoped browser. The test-scoped fixture sees that proxy again;
|
|
337
|
+
// treating our own proxy as a fresh target would nest wrappers, emit two
|
|
338
|
+
// phases for one operation, and perform every browser activation twice.
|
|
339
|
+
this.proxyCache.set(proxy, proxy);
|
|
332
340
|
return proxy;
|
|
333
341
|
}
|
|
334
342
|
createPhase(kind, operation, causedByPhaseId, source = callerSource()) {
|
|
@@ -467,7 +475,7 @@ class CoveragePhaseController {
|
|
|
467
475
|
let activeController;
|
|
468
476
|
let bridgedAssertionDepth = 0;
|
|
469
477
|
const controllers = new Map();
|
|
470
|
-
const directRuntime = () => globalThis.__SUPERCOV_DIRECT_RUNTIME__;
|
|
478
|
+
const directRuntime = () => globalThis.__SUPERCOV_DIRECT_RUNTIME__ ?? coverageRuntime;
|
|
471
479
|
globalThis.__SUPERCOV_ASSERTION_PHASE_BRIDGE__ = (operation, source, callback) => {
|
|
472
480
|
const controller = activeController;
|
|
473
481
|
const runtime = directRuntime();
|
|
@@ -829,8 +837,6 @@ const instrumentedTest = base.extend({
|
|
|
829
837
|
// Emit an artifact even when this test touched no application source.
|
|
830
838
|
// A complete test-to-coverage matrix must also identify tests that are
|
|
831
839
|
// removable without changing coverage.
|
|
832
|
-
const outputPath = testInfo.outputPath("mcdc.json");
|
|
833
|
-
mkdirSync(dirname(outputPath), { recursive: true });
|
|
834
840
|
const testFile = relative(process.cwd(), testInfo.file)
|
|
835
841
|
.split(sep)
|
|
836
842
|
.join("/");
|
|
@@ -854,7 +860,6 @@ const instrumentedTest = base.extend({
|
|
|
854
860
|
server,
|
|
855
861
|
};
|
|
856
862
|
const serialized = `${JSON.stringify(payload)}\n`;
|
|
857
|
-
atomicWriteFileSync(outputPath, serialized);
|
|
858
863
|
// Pool runners may cycle-restore a VM immediately after Playwright
|
|
859
864
|
// exits, which can discard or overwrite the normal artifact copy.
|
|
860
865
|
// Write one uniquely named, one-shot evidence file to the runner's
|
|
@@ -865,10 +870,10 @@ const instrumentedTest = base.extend({
|
|
|
865
870
|
: GENERATED_EVIDENCE_DIRECTORY);
|
|
866
871
|
if (evidenceDirectory) {
|
|
867
872
|
const resolvedDirectory = resolve(process.cwd(), evidenceDirectory);
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
873
|
+
const append = process.env.SUPERCOV_DURABLE_EVIDENCE_EACH_TEST === "1"
|
|
874
|
+
? appendJsonLineDurableSync
|
|
875
|
+
: appendJsonLineSync;
|
|
876
|
+
append(resolve(resolvedDirectory, `playwright-worker-${evidenceWriterIdentity()}-${process.pid}.mcdc.jsonl`), serialized);
|
|
872
877
|
}
|
|
873
878
|
}
|
|
874
879
|
finally {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
2
1
|
import { relative, resolve, sep } from "node:path";
|
|
3
2
|
import { inferTestProvenance } from "./provenance.js";
|
|
4
|
-
import {
|
|
3
|
+
import { appendJsonLineDurableSync, appendJsonLineSync } from "./atomic.js";
|
|
5
4
|
const GENERATED_EVIDENCE_DIRECTORY = "__SUPERCOV_EVIDENCE_DIRECTORY__";
|
|
5
|
+
const evidenceWriterIdentity = () => (process.env.SUPERCOV_EXECUTION_LOG_SHARD ?? `pid-${process.pid}`)
|
|
6
|
+
.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
6
7
|
/** Records outcomes even when browser or fixture startup fails before coverage. */
|
|
7
8
|
export default class SupercovPlaywrightReporter {
|
|
9
|
+
records = [];
|
|
8
10
|
onTestEnd(test, result) {
|
|
9
11
|
const evidenceDirectory = process.env["SUPERCOV_EVIDENCE_DIR"] ??
|
|
10
12
|
(GENERATED_EVIDENCE_DIRECTORY.startsWith("__")
|
|
@@ -32,9 +34,18 @@ export default class SupercovPlaywrightReporter {
|
|
|
32
34
|
browser: [],
|
|
33
35
|
server: [],
|
|
34
36
|
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
this.records.push(payload);
|
|
38
|
+
}
|
|
39
|
+
onEnd() {
|
|
40
|
+
const evidenceDirectory = process.env["SUPERCOV_EVIDENCE_DIR"] ??
|
|
41
|
+
(GENERATED_EVIDENCE_DIRECTORY.startsWith("__")
|
|
42
|
+
? undefined
|
|
43
|
+
: GENERATED_EVIDENCE_DIRECTORY);
|
|
44
|
+
if (!evidenceDirectory || this.records.length === 0)
|
|
45
|
+
return;
|
|
46
|
+
const append = process.env.SUPERCOV_DURABLE_EVIDENCE_EACH_TEST === "1"
|
|
47
|
+
? appendJsonLineDurableSync
|
|
48
|
+
: appendJsonLineSync;
|
|
49
|
+
append(resolve(process.cwd(), evidenceDirectory, `playwright-status-${evidenceWriterIdentity()}-${process.pid}.mcdc.jsonl`), `${this.records.map(record => JSON.stringify(record)).join("\n")}\n`);
|
|
39
50
|
}
|
|
40
51
|
}
|
|
@@ -16,7 +16,9 @@ installLaunchSupervisor();
|
|
|
16
16
|
// run and periodically reports public active-resource types. This deliberately
|
|
17
17
|
// uses no Unix signal: a launch tree may contain uninstrumented Node children,
|
|
18
18
|
// and signalling one would terminate a healthy command by default.
|
|
19
|
-
|
|
19
|
+
const verboseDiagnostics = [process.env.SUPERCOV_VERBOSE, process.env.SUPERCOV_DEBUG]
|
|
20
|
+
.some(value => value === "1" || value === "true" || value === "yes");
|
|
21
|
+
if (verboseDiagnostics && !process.__SUPERCOV_DIAGNOSTIC_REPORTER__) {
|
|
20
22
|
process.__SUPERCOV_DIAGNOSTIC_REPORTER__ = true;
|
|
21
23
|
const ownerFile = process.env.SUPERCOV_DIAGNOSTIC_OWNER_FILE;
|
|
22
24
|
let ownerDescriptor;
|
|
@@ -62,11 +64,18 @@ if (!process.__SUPERCOV_DIAGNOSTIC_REPORTER__) {
|
|
|
62
64
|
});
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
process.
|
|
67
|
+
// Runner adapters and instrumented modules import the runtime when they need
|
|
68
|
+
// it. Keeping the preload itself thin avoids evaluating the full collector in
|
|
69
|
+
// npm launchers, web-server supervisors, and other Node children that never
|
|
70
|
+
// execute measured JavaScript.
|
|
71
|
+
if (process.env.SUPERCOV_DURABLE_EVIDENCE_EACH_TEST === "1") {
|
|
72
|
+
// A translated remote/VM command can execute an ahead-of-run transformed
|
|
73
|
+
// test through an opaque runner that bypasses the ordinary Playwright or
|
|
74
|
+
// node:test import boundary. The remote-launch adapter marks that process;
|
|
75
|
+
// initialize the runtime before its transformed module can evaluate.
|
|
76
|
+
globalThis.__SUPERCOV_DIRECT_RUNTIME__ ??= await import("./runtime.js");
|
|
77
|
+
process.__SUPERCOV_DIRECT_RUNTIME__ ??= globalThis.__SUPERCOV_DIRECT_RUNTIME__;
|
|
78
|
+
}
|
|
70
79
|
// Workers are independent Node processes and an explicit `execArgv: []`
|
|
71
80
|
// otherwise strips the preload that supplies the isolated runtime. Preserve
|
|
72
81
|
// every user option while adding exactly one Supercov import.
|
|
@@ -105,6 +114,7 @@ const generatedPlaywrightConfig = process.env.SUPERCOV_GENERATED_PLAYWRIGHT_CONF
|
|
|
105
114
|
const generatedJestConfig = process.env.SUPERCOV_GENERATED_JEST_CONFIG;
|
|
106
115
|
const entrypoint = process.argv[1]?.replaceAll("\\", "/") ?? "";
|
|
107
116
|
const playwrightTarget = process.env.SUPERCOV_PLAYWRIGHT_MODULE;
|
|
117
|
+
const projectRoot = process.env.SUPERCOV_PROJECT_ROOT?.replaceAll("\\", "/").replace(/\/$/, "");
|
|
108
118
|
const nodeTestWrapper = new URL("./nodeTest.js", import.meta.url).href;
|
|
109
119
|
const nodeAssertWrapper = new URL("./nodeAssert.js", import.meta.url).href;
|
|
110
120
|
const nodeAssertStrictWrapper = new URL("./nodeAssertStrict.js", import.meta.url).href;
|
|
@@ -188,6 +198,7 @@ if (generatedPlaywrightConfig &&
|
|
|
188
198
|
}
|
|
189
199
|
if (process.env.SUPERCOV_CJS_INTERCEPT === "1" &&
|
|
190
200
|
process.env.SUPERCOV_INSIDE_PLAYWRIGHT === "1" &&
|
|
201
|
+
!isPlaywrightEntrypoint &&
|
|
191
202
|
process.env.SUPERCOV_INSIDE_VITEST !== "1" &&
|
|
192
203
|
process.env.VITEST !== "true") {
|
|
193
204
|
const target = playwrightTarget ?? "@playwright/test";
|
|
@@ -220,7 +231,18 @@ if (process.env.SUPERCOV_CJS_INTERCEPT === "1" &&
|
|
|
220
231
|
// itself imports the native built-in from Supercov's generated directory, so
|
|
221
232
|
// only first-party callers are redirected and recursion is impossible.
|
|
222
233
|
if (process.env.SUPERCOV_CJS_INTERCEPT === "1") {
|
|
223
|
-
const
|
|
234
|
+
const entrypointBelongsToProject = Boolean(projectRoot && entrypoint.startsWith(`${projectRoot}/`));
|
|
235
|
+
const runnerCanLoadProjectCode = entrypointBelongsToProject ||
|
|
236
|
+
entrypoint === "" ||
|
|
237
|
+
process.env.SUPERCOV_INSIDE_VITEST === "1" ||
|
|
238
|
+
(process.env.SUPERCOV_INSIDE_PLAYWRIGHT === "1" && !isPlaywrightEntrypoint);
|
|
239
|
+
if (!runnerCanLoadProjectCode) {
|
|
240
|
+
// Package-manager and Playwright coordinator processes propagate the
|
|
241
|
+
// preload to their children but never evaluate project test modules.
|
|
242
|
+
// Their worker/child entrypoints install these synchronous CJS hooks.
|
|
243
|
+
// Avoid paying to import three runner adapters in every launcher.
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
224
246
|
const nodeTestAdapter = await import(__rewriteRelativeImportExtension(nodeTestWrapper));
|
|
225
247
|
const cjsNodeTestAdapter = Object.assign(nodeTestAdapter.test, nodeTestAdapter);
|
|
226
248
|
const nodeAssertAdapter = await import(__rewriteRelativeImportExtension(nodeAssertWrapper));
|
|
@@ -246,4 +268,5 @@ if (process.env.SUPERCOV_CJS_INTERCEPT === "1") {
|
|
|
246
268
|
: cjsNodeAssertAdapter;
|
|
247
269
|
return originalLoad.call(this, request, parent, isMain);
|
|
248
270
|
};
|
|
271
|
+
}
|
|
249
272
|
}
|
|
@@ -1014,6 +1014,52 @@ function mcdcEnd(frame, value) {
|
|
|
1014
1014
|
}
|
|
1015
1015
|
return value;
|
|
1016
1016
|
}
|
|
1017
|
+
const directRuntimeApi = {
|
|
1018
|
+
activateCoverageScope,
|
|
1019
|
+
beginBufferedServerEvidence,
|
|
1020
|
+
bindCoverageContext,
|
|
1021
|
+
coverageCarrier,
|
|
1022
|
+
coverageContextEnvironment,
|
|
1023
|
+
coverageContextHeaders,
|
|
1024
|
+
coverageHit,
|
|
1025
|
+
coverageHitV2,
|
|
1026
|
+
coverageSnapshot,
|
|
1027
|
+
decodeProbeV2Vector,
|
|
1028
|
+
defaultEntered,
|
|
1029
|
+
defaultSelected,
|
|
1030
|
+
enableRuntimeSnapshotEvidence,
|
|
1031
|
+
flushBufferedBackgroundEvidence,
|
|
1032
|
+
flushBufferedServerEvidence,
|
|
1033
|
+
loopBegin,
|
|
1034
|
+
loopEnd,
|
|
1035
|
+
loopEntered,
|
|
1036
|
+
mcdcBegin,
|
|
1037
|
+
mcdcCondition,
|
|
1038
|
+
mcdcEnd,
|
|
1039
|
+
mcdcEndV2,
|
|
1040
|
+
optionalCallBegin,
|
|
1041
|
+
optionalCallContinued,
|
|
1042
|
+
optionalCallEnd,
|
|
1043
|
+
optionalCallReached,
|
|
1044
|
+
optionalSelect,
|
|
1045
|
+
parenthesizedAssignmentValue,
|
|
1046
|
+
registerProbeV2,
|
|
1047
|
+
resetCoverage,
|
|
1048
|
+
selectionBegin,
|
|
1049
|
+
selectionEnd,
|
|
1050
|
+
selectionRight,
|
|
1051
|
+
takeNodeAssertionPhases,
|
|
1052
|
+
tryBegin,
|
|
1053
|
+
tryCatch,
|
|
1054
|
+
tryEnd,
|
|
1055
|
+
withCoverageCarrier,
|
|
1056
|
+
withNodeAssertionPhase,
|
|
1057
|
+
withRequestPhase,
|
|
1058
|
+
writeExclusiveBackgroundRecord
|
|
1059
|
+
};
|
|
1060
|
+
globalThis.__SUPERCOV_DIRECT_RUNTIME__ ??= directRuntimeApi;
|
|
1061
|
+
if (typeof process !== "undefined")
|
|
1062
|
+
process.__SUPERCOV_DIRECT_RUNTIME__ ??= directRuntimeApi;
|
|
1017
1063
|
export {
|
|
1018
1064
|
activateCoverageScope,
|
|
1019
1065
|
beginBufferedServerEvidence,
|