supercov 0.0.16 → 0.0.18
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 +21 -7
- package/bin/supercov.js +10 -1
- package/docs/agent-loop.md +157 -0
- package/docs/cli.md +145 -0
- package/docs/coverage-model.md +134 -0
- package/docs/evidence.md +122 -0
- package/docs/getting-started.md +145 -0
- package/docs/supported-suites.md +119 -0
- package/docs/verification.md +102 -0
- package/docs/workspace-isolation.md +11 -9
- package/package.json +11 -7
- package/runtime/javascript/launchSupervisor.js +30 -0
- package/runtime/javascript/playwright.js +2 -2
- package/runtime/javascript/runtime.js +51 -25
- package/runtime/javascript/vitestReporter.js +23 -3
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
Supercov measures coverage for JavaScript, TypeScript, and Rust test suites.
|
|
4
|
+
Prefix the command you already run; no config file, import, or reporter is
|
|
5
|
+
required.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx supercov -- npm test
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Everything after `--` is your command, executed exactly as written.
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
| Requirement | Detail |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| Node.js | 22 or newer |
|
|
18
|
+
| Project | JavaScript or TypeScript, with a runnable test command |
|
|
19
|
+
| Disk | A `.supercov/` directory in the project root, which Supercov creates |
|
|
20
|
+
|
|
21
|
+
Nothing else is required. Supercov never contacts a network service, and no
|
|
22
|
+
part of your source or evidence leaves the machine.
|
|
23
|
+
|
|
24
|
+
## Your first run
|
|
25
|
+
|
|
26
|
+
From the project root:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npx supercov -- npm test
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The run prints its phases as it goes — initialization, workspace preparation,
|
|
33
|
+
adapter setup, the instrumented build, your unchanged test command, and
|
|
34
|
+
evidence publication — and finishes by publishing one immutable run under
|
|
35
|
+
`.supercov/runs/<run-id>/`. The run id is a UTC timestamp, so run ids sort
|
|
36
|
+
chronologically.
|
|
37
|
+
|
|
38
|
+
If the command you normally use is not `npm test`, use that instead:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
npx supercov -- npx playwright test
|
|
42
|
+
npx supercov -- pnpm test:e2e
|
|
43
|
+
npx supercov -- npm run test:unit && npx supercov -- npm run test:e2e
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
A single Supercov run can collect several runners. Coverage from a command that
|
|
47
|
+
launches Vitest and Playwright ends up in one run, with each test labelled by
|
|
48
|
+
the runner that executed it.
|
|
49
|
+
|
|
50
|
+
## Read the result
|
|
51
|
+
|
|
52
|
+
Start with the summary, then narrow. Every query names one run; `latest`
|
|
53
|
+
selects the newest local run.
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
# What runs exist?
|
|
57
|
+
npx supercov runs --limit 5
|
|
58
|
+
|
|
59
|
+
# How complete is the newest one?
|
|
60
|
+
npx supercov runs latest
|
|
61
|
+
|
|
62
|
+
# Which files hold the most open obligations?
|
|
63
|
+
npx supercov runs latest gaps --limit 10
|
|
64
|
+
|
|
65
|
+
# What exactly is open in one file?
|
|
66
|
+
npx supercov runs latest file app/checkout/session.ts
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Output is written for an agent reading a terminal: short, paginated, and
|
|
70
|
+
carrying a copyable next-page command. Add `--json` to any query for the stable
|
|
71
|
+
machine format.
|
|
72
|
+
|
|
73
|
+
## Add a test and prove it landed
|
|
74
|
+
|
|
75
|
+
Write a test the normal way, then re-run and compare:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
npx supercov -- npm test
|
|
79
|
+
npx supercov diff <previous-run-id> latest
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`diff` reports what the newer run covers that the older one did not. To check
|
|
83
|
+
one specific test's contribution rather than the whole run:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
npx supercov runs latest test "rejects a locked order"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## What Supercov writes
|
|
90
|
+
|
|
91
|
+
Supercov owns two marker-protected locations inside your project:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
.supercov/
|
|
95
|
+
runs/<run-id>/evidence.raw.gz exact denominator manifest + raw evidence
|
|
96
|
+
runs/<run-id>/run.json fingerprints, phase timings, integrity
|
|
97
|
+
supercov/
|
|
98
|
+
workspace/<project>/ isolated build namespace, reused between runs
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Your source files, test files, runner configuration and ordinary build output
|
|
102
|
+
are never modified, overwritten or rebuilt. Both owned locations carry their
|
|
103
|
+
own gitignore; an existing user `supercov/` directory is never adopted.
|
|
104
|
+
|
|
105
|
+
Storage is bounded by you, not by a background process:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
npx supercov clean # remove every stored run and build cache
|
|
109
|
+
npx supercov clean --keep 20 # retain the 20 newest runs
|
|
110
|
+
npx supercov clean --keep 20 --dry-run # show what would be removed
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Choosing what counts
|
|
114
|
+
|
|
115
|
+
Two options change the meaning of a number rather than its presentation, so
|
|
116
|
+
they are worth knowing early.
|
|
117
|
+
|
|
118
|
+
`--filter` selects which attempts contribute:
|
|
119
|
+
|
|
120
|
+
- `all` (default) counts every executed attempt, including attempts that later
|
|
121
|
+
failed. This matches what conventional coverage tools report.
|
|
122
|
+
- `passed` counts only successful attempts of tests that ultimately passed —
|
|
123
|
+
verified coverage.
|
|
124
|
+
- `failed` counts only failed attempts, which is useful when diagnosing a flaky
|
|
125
|
+
test's real execution path.
|
|
126
|
+
|
|
127
|
+
`--kind` selects a semantic test level such as `e2e`, `integration`,
|
|
128
|
+
`component` or `unit`. Kind is resolved from an explicit `SUPERCOV_TEST_KIND`,
|
|
129
|
+
then the Playwright project name, then the test path, then the runner default.
|
|
130
|
+
Queries record how the label was established, so an inferred kind is never
|
|
131
|
+
presented as one you declared.
|
|
132
|
+
|
|
133
|
+
Filtered queries recompute every obligation from the selected tests instead of
|
|
134
|
+
filtering an already-computed percentage. This matters most for MC/DC, where a
|
|
135
|
+
witness pair assembled from one unit vector and one end-to-end vector counts for
|
|
136
|
+
the combined suite but not for either level alone.
|
|
137
|
+
|
|
138
|
+
## Where to go next
|
|
139
|
+
|
|
140
|
+
- [Agent loop](/docs/agent-loop) — the unattended workflow this is designed for.
|
|
141
|
+
- [CLI reference](/docs/cli) — every command and flag.
|
|
142
|
+
- [Coverage model](/docs/coverage-model) — what an obligation is, and why the
|
|
143
|
+
denominator is larger than lines and branches.
|
|
144
|
+
- [Supported suites](/docs/supported-suites) — where attribution is exact and
|
|
145
|
+
where it is aggregate.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Supported suites
|
|
2
|
+
|
|
3
|
+
Supercov wraps a test command and instruments the processes it launches.
|
|
4
|
+
Runner support differs by attribution level: exact per-test attribution or
|
|
5
|
+
aggregate coverage.
|
|
6
|
+
|
|
7
|
+
## Attribution by runner
|
|
8
|
+
|
|
9
|
+
| Runner | Attribution | Notes |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Playwright | Exact per test | Test, worker, retry and outcome scopes. ESM and CommonJS specs in arbitrary directories, plus project-owned fixture packages. |
|
|
12
|
+
| Vitest | Exact per test | Module-import and setup execution is kept as a separate setup scope. |
|
|
13
|
+
| Jest | Exact per test | Including concurrent and parameterized tests. |
|
|
14
|
+
| `node:test` | Exact per test | Through the generated adapter. |
|
|
15
|
+
| AVA, Mocha, other runners | Aggregate only | First-party structural coverage through inherited process instrumentation. Hits are recorded as background rather than guessed onto tests. |
|
|
16
|
+
| Browser component runners without an adapter | Aggregate only | Same boundary, made explicit in the report. |
|
|
17
|
+
|
|
18
|
+
Adapters are generated into the isolated workspace. Your test imports, reporter
|
|
19
|
+
list and runner configuration are not modified.
|
|
20
|
+
|
|
21
|
+
A single command may collect several runners into one run. Each test is then
|
|
22
|
+
labelled with the runner that executed it and the semantic kind it belongs to.
|
|
23
|
+
|
|
24
|
+
## Builds
|
|
25
|
+
|
|
26
|
+
| Project shape | How instrumentation is applied |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| Vite or Vitest | Through the existing Vite graph |
|
|
29
|
+
| Next, Turbopack, Webpack, esbuild, SWC, other build commands | Applied to the disposable source copy, then your unchanged build command runs against it |
|
|
30
|
+
| No build step (ESM or CommonJS) | Direct instrumentation of the disposable source copy |
|
|
31
|
+
|
|
32
|
+
The ordinary application build is never read as an input, overwritten, or
|
|
33
|
+
rebuilt afterwards.
|
|
34
|
+
|
|
35
|
+
When the complete source, configuration and toolchain fingerprint is unchanged
|
|
36
|
+
between runs, the previous instrumented output and manifest are carried into the
|
|
37
|
+
refreshed workspace and the build is skipped entirely.
|
|
38
|
+
|
|
39
|
+
### Build-only environment flags
|
|
40
|
+
|
|
41
|
+
Before the isolated build, Supercov compares the invoked npm, pnpm, yarn or bun
|
|
42
|
+
script with explicit string-valued `process.env` checks in the project's build
|
|
43
|
+
configuration. A semantic match — a `test:preview` script and a
|
|
44
|
+
`process.env.TEST_PREVIEW === "true"` check, for example — activates that
|
|
45
|
+
build-only flag, and the decision is printed before the build. Values are never
|
|
46
|
+
guessed for unrelated environment variables.
|
|
47
|
+
|
|
48
|
+
## Browsers
|
|
49
|
+
|
|
50
|
+
The compatibility workflow exercises Chromium, Firefox and WebKit, along with
|
|
51
|
+
Node 22, 24 and 25, Playwright 1.55 and current, Vite 5 and current, Vitest 2
|
|
52
|
+
and current, and modern JavaScript, JSX, TypeScript and TSX syntax fixtures.
|
|
53
|
+
|
|
54
|
+
The Playwright adapter covers the `page` and `request` fixtures, API request
|
|
55
|
+
contexts, user-created browser contexts and pages, popups and all of their
|
|
56
|
+
frames, dedicated and service workers, WebSocket handshake headers, and
|
|
57
|
+
test-spawned child processes.
|
|
58
|
+
|
|
59
|
+
For Chromium documents exposed through the page target, a pre-document probe
|
|
60
|
+
installs the action phase before application JavaScript starts. A newly created
|
|
61
|
+
cross-origin iframe may run in a separate target that cannot be safely paused
|
|
62
|
+
during navigation; its earliest probes use a timing fallback until the frame is
|
|
63
|
+
live. This affects action-level causal precision only — never structural
|
|
64
|
+
coverage or test attribution.
|
|
65
|
+
|
|
66
|
+
## Servers, background work and child processes
|
|
67
|
+
|
|
68
|
+
Server-side coverage is safe when Playwright runs multiple workers against one
|
|
69
|
+
application server. Every routed request carries a run, worker, test and retry
|
|
70
|
+
scope; Node async context retains that scope and its current phase across
|
|
71
|
+
awaited work; and each worker writes to a distinct attempt path that only its
|
|
72
|
+
own collecting fixture will accept.
|
|
73
|
+
|
|
74
|
+
Detached work is never dropped silently or guessed onto whichever test is
|
|
75
|
+
active:
|
|
76
|
+
|
|
77
|
+
- HTTP callbacks inherit the carrier automatically.
|
|
78
|
+
- Child processes inherit it through their environment.
|
|
79
|
+
- Exported queue helpers cover BullMQ, Bee-Queue, pg-boss, Agenda and
|
|
80
|
+
in-process schedulers.
|
|
81
|
+
- Anything that still arrives without a carrier is persisted under the
|
|
82
|
+
background scope.
|
|
83
|
+
|
|
84
|
+
## Remote and containerised execution
|
|
85
|
+
|
|
86
|
+
Discovery is structural rather than provider-specific. The preload and a
|
|
87
|
+
narrowly gated ESM transform look for a static `build(options)` capability,
|
|
88
|
+
activate only when those options contain a host-to-guest mount that includes the
|
|
89
|
+
isolated project, scope any existing cache or snapshot identity to the run's
|
|
90
|
+
source fingerprint, and follow the returned object graph. A method whose options
|
|
91
|
+
contain `argv`, `cmd` or `command` receives guest-translated Supercov paths and
|
|
92
|
+
a guest-valid Node preload.
|
|
93
|
+
|
|
94
|
+
The execution log records this process and capability graph, but hashes long or
|
|
95
|
+
multiline arguments so embedded shell bodies and credentials are never
|
|
96
|
+
persisted.
|
|
97
|
+
|
|
98
|
+
The boundary is explicit: Supercov follows Node child processes, not arbitrary
|
|
99
|
+
non-Node supervisors, and not a remote control plane that never exposes its
|
|
100
|
+
launches to the local process. CommonJS and pure-ESM executor SDKs,
|
|
101
|
+
object-shaped and positional execution APIs, and opaque returned object graphs
|
|
102
|
+
are all covered when a discoverable build capability exposes the workspace mount
|
|
103
|
+
and an execution capability accepts an environment. Anything that hides all
|
|
104
|
+
launch state behind an out-of-process RPC needs a dedicated adapter, and
|
|
105
|
+
Supercov reports missing evidence rather than claiming those paths are covered.
|
|
106
|
+
|
|
107
|
+
The public regression suite includes provider-neutral CommonJS and pure-ESM
|
|
108
|
+
opaque executors. CI requires Supercov to discover that structure, scope the
|
|
109
|
+
cache identity, translate paths and the Node preload into the guest, run nested
|
|
110
|
+
Vitest and Playwright commands, parse every concurrent trace shard, and produce
|
|
111
|
+
100% fixture coverage.
|
|
112
|
+
|
|
113
|
+
## Distributed runs
|
|
114
|
+
|
|
115
|
+
Each shard produces its own immutable run. `supercov merge` combines runs whose
|
|
116
|
+
source, test, dependency, configuration, instrumenter, schema and denominator
|
|
117
|
+
fingerprints match exactly, publishes a new immutable run atomically, and leaves
|
|
118
|
+
every input untouched. Incompatible shards fail with a clear reason instead of
|
|
119
|
+
being averaged together.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Verification
|
|
2
|
+
|
|
3
|
+
Supercov instruments source before the test command runs. Seven release gates
|
|
4
|
+
check that the instrumented program matches the original program and that
|
|
5
|
+
coverage calculations are correct. Any failed gate blocks publication.
|
|
6
|
+
|
|
7
|
+
## 1. Semantic differential execution
|
|
8
|
+
|
|
9
|
+
Original and instrumented programs are executed in isolated scopes and compared
|
|
10
|
+
on three axes: return values, thrown errors, and the observable order of side
|
|
11
|
+
effects.
|
|
12
|
+
|
|
13
|
+
The fixtures deliberately target the places where a naive transform breaks:
|
|
14
|
+
getters, proxies, optional calls and `this` binding, computed logical
|
|
15
|
+
assignments, parameter defaults, `try`/`catch`/`finally`, iterator closing,
|
|
16
|
+
switch fallthrough, labelled loops, async functions and generators.
|
|
17
|
+
|
|
18
|
+
## 2. Deterministic generated corpus
|
|
19
|
+
|
|
20
|
+
A generated corpus exercises 160 nested combinations of short-circuiting,
|
|
21
|
+
ternaries, coercion and thrown expressions on every run. It is deterministic, so
|
|
22
|
+
a regression reproduces exactly rather than appearing once in CI and never
|
|
23
|
+
again.
|
|
24
|
+
|
|
25
|
+
## 3. Property testing
|
|
26
|
+
|
|
27
|
+
Seeded `fast-check` properties generate a further 500 nested expressions and 300
|
|
28
|
+
control-flow executions per run, with shrinking and a reproducible seed printed
|
|
29
|
+
on failure.
|
|
30
|
+
|
|
31
|
+
## 4. Coverage oracles
|
|
32
|
+
|
|
33
|
+
Behaviour equivalence is not enough — the numbers have to be right too. Separate
|
|
34
|
+
oracles assert exact decision vectors, MC/DC witnesses and branch alternatives
|
|
35
|
+
independently of what the program does.
|
|
36
|
+
|
|
37
|
+
## 5. An independent MC/DC implementation
|
|
38
|
+
|
|
39
|
+
The same three-condition masking-MC/DC golden cases must produce identical
|
|
40
|
+
verdicts under Supercov and under Clang/LLVM source-based MC/DC: 100% for a
|
|
41
|
+
complete witness set and 33.33% for an incomplete one.
|
|
42
|
+
|
|
43
|
+
This is the gate that matters most. MC/DC has enough subtlety — masking versus
|
|
44
|
+
unique-cause, short-circuit evaluation, compound conditions — that agreement
|
|
45
|
+
with an independently implemented, widely audited toolchain is far stronger
|
|
46
|
+
evidence than any self-consistent test suite.
|
|
47
|
+
|
|
48
|
+
## 6. TC39 Test262
|
|
49
|
+
|
|
50
|
+
Release CI shards the pinned Test262 corpus across 16 workers, runs the official
|
|
51
|
+
harness against original and instrumented sources, and rejects any scenario that
|
|
52
|
+
passes originally but fails after transformation.
|
|
53
|
+
|
|
54
|
+
Some categories are excluded by construction, with reason counts printed for
|
|
55
|
+
every shard:
|
|
56
|
+
|
|
57
|
+
| Excluded | Why |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| Module, async and raw tests | Not comparable under the source-rewrite harness |
|
|
60
|
+
| Parse and resolution negatives | The transform never runs on unparseable input |
|
|
61
|
+
| Annex B sloppy-script extensions | Does not apply to the application modules Supercov instruments |
|
|
62
|
+
| `Function.prototype.toString` and function-source coercion | Exact source reflection necessarily observes a source transform |
|
|
63
|
+
|
|
64
|
+
The last category is handled in the product, not hidden: when application code
|
|
65
|
+
directly coerces or observes a function's source, Supercov leaves that body
|
|
66
|
+
uninstrumented and records a visible `semantic-safety` completeness blocker.
|
|
67
|
+
Dedicated differential fixtures cover the async and generator cases that Test262
|
|
68
|
+
cannot compare.
|
|
69
|
+
|
|
70
|
+
## 7. Performance budgets
|
|
71
|
+
|
|
72
|
+
Transform latency, transactional workspace preparation, output expansion and
|
|
73
|
+
runtime probe overhead are each checked against explicit budgets. A change that
|
|
74
|
+
makes instrumentation correct but unusably slow fails the same way a wrong
|
|
75
|
+
answer does.
|
|
76
|
+
|
|
77
|
+
## Cross-platform and compatibility gates
|
|
78
|
+
|
|
79
|
+
Alongside the seven correctness gates, the compatibility workflow runs Node 22,
|
|
80
|
+
24 and 25, Playwright 1.55 and current, Vite 5 and current, Vitest 2 and
|
|
81
|
+
current, Chromium, Firefox and WebKit, and modern JavaScript, JSX, TypeScript
|
|
82
|
+
and TSX syntax fixtures.
|
|
83
|
+
|
|
84
|
+
Filesystem publication, symlink handling, copy fallback, `ENOSPC`, failed
|
|
85
|
+
rename and forced-termination recovery are all exercised on Ubuntu, macOS and
|
|
86
|
+
Windows.
|
|
87
|
+
|
|
88
|
+
A clean-room gate packs the npm tarball, invokes it through `npx` in a project
|
|
89
|
+
with no build step, and asserts that not a single source or configuration file
|
|
90
|
+
changed.
|
|
91
|
+
|
|
92
|
+
## Running the gates yourself
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
npm test
|
|
96
|
+
npm run test:clang-mcdc
|
|
97
|
+
npm run benchmark:check
|
|
98
|
+
TEST262_DIR=/path/to/test262 npm run test:test262
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The Clang/LLVM oracle requires `clang` and `llvm` to be installed. The Test262
|
|
102
|
+
gate requires a checkout of the pinned corpus.
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# Workspace isolation
|
|
2
2
|
|
|
3
|
-
Supercov
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
depend on a signal handler running.
|
|
3
|
+
Supercov writes generated and temporary files only under the project's
|
|
4
|
+
`.supercov/` directory. Application source and ordinary build artifacts are
|
|
5
|
+
not write targets, and cleanup does not depend on a signal handler.
|
|
7
6
|
|
|
8
7
|
## Owned paths
|
|
9
8
|
|
|
@@ -17,10 +16,10 @@ is below the project's `.supercov/` directory:
|
|
|
17
16
|
| `work/<run>/run-publication/` | Incomplete run staging; atomically renamed or removed on recovery. |
|
|
18
17
|
| `evidence/<run>/` | Loose in-flight evidence; packed and removed after publication. |
|
|
19
18
|
| `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
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
19
|
+
| `supercov/workspace/<project>/` | Stable physical fallback and provider snapshot cache. The non-dotted ancestor keeps Express/`send` and similar static-file stacks semantically unchanged. |
|
|
20
|
+
| `supercov/workspace/.<project>.staging-*` | Unpublished cache transaction; removed on error or recovery. |
|
|
21
|
+
| `supercov/workspace/.<project>.previous-*` | Last complete cache generation during publication; restored or removed on recovery. |
|
|
22
|
+
| `supercov/workspace/<project>/.supercov/server-evidence/<run>/` | Server/background transport shared with local or mounted guest processes; archived and removed after publication, interruption, refresh, or cleanup. |
|
|
24
23
|
|
|
25
24
|
The lower-level runtime retains `/tmp/supercov-server-evidence` only as a
|
|
26
25
|
fallback when it is embedded without the Supercov CLI and no owned transport
|
|
@@ -30,7 +29,10 @@ translation maps that same root into the guest mount.
|
|
|
30
29
|
## Current physical fallback
|
|
31
30
|
|
|
32
31
|
Builds and opaque VM/container mounts currently use a stable physical namespace
|
|
33
|
-
at
|
|
32
|
+
at `supercov/workspace/<project>`. The `supercov/` container is owned only when
|
|
33
|
+
its exact marker is present; if a project already owns that name, Supercov uses
|
|
34
|
+
a deterministic non-dotted fallback and copies the user's directory as ordinary
|
|
35
|
+
source rather than adopting or excluding it. Refresh has four states:
|
|
34
36
|
|
|
35
37
|
1. The last complete source generation remains live while a sibling `staging`
|
|
36
38
|
tree is prepared.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supercov",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Zero-edit, runner-aware coverage completeness for JavaScript test suites",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
"build": "cargo build -p supercov",
|
|
37
37
|
"sync:rust-assets": "node scripts/sync-rust-package-assets.mjs",
|
|
38
38
|
"test:rust-assets": "node scripts/sync-rust-package-assets.mjs --check",
|
|
39
|
+
"test:rustc-backend-spike": "node scripts/rustc-backend-spike.mjs",
|
|
40
|
+
"test:rust-compiler-spikes": "cargo build -p supercov && node scripts/rust-libtest-companion-spike.mjs && node scripts/rust-async-attribution-spike.mjs && node scripts/rust-subprocess-attribution-spike.mjs && node scripts/rust-custom-harness-spike.mjs && node scripts/rust-libtest-builder-lifecycle-spike.mjs",
|
|
41
|
+
"test:rust-cargo-runner": "cargo build -p supercov && node scripts/rust-cargo-runner-integration.mjs",
|
|
42
|
+
"test:rust-nextest-runner": "cargo build -p supercov && node scripts/rust-nextest-runner-integration.mjs",
|
|
39
43
|
"test": "cargo test --workspace",
|
|
40
44
|
"test:runtime": "node --test tests/runtime/*.test.mjs",
|
|
41
45
|
"test:fixture": "cargo build -p supercov && node scripts/rust-fixture-matrix.mjs",
|
|
@@ -63,12 +67,12 @@
|
|
|
63
67
|
"prepublishOnly": "npm run release:check"
|
|
64
68
|
},
|
|
65
69
|
"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.
|
|
70
|
+
"@supercov/cli-darwin-arm64": "0.0.18",
|
|
71
|
+
"@supercov/cli-darwin-x64": "0.0.18",
|
|
72
|
+
"@supercov/cli-linux-arm64-gnu": "0.0.18",
|
|
73
|
+
"@supercov/cli-linux-arm64-musl": "0.0.18",
|
|
74
|
+
"@supercov/cli-linux-x64-gnu": "0.0.18",
|
|
75
|
+
"@supercov/cli-linux-x64-musl": "0.0.18"
|
|
72
76
|
},
|
|
73
77
|
"peerDependencies": {
|
|
74
78
|
"@playwright/test": ">=1.55.0",
|
|
@@ -19,6 +19,17 @@ const importedCapabilityProxies = new WeakMap();
|
|
|
19
19
|
const importedMemberProxies = new WeakMap();
|
|
20
20
|
let installed = false;
|
|
21
21
|
let remoteLaunchSequence = 0;
|
|
22
|
+
|
|
23
|
+
function isClassConstructor(value) {
|
|
24
|
+
if (typeof value !== "function")
|
|
25
|
+
return false;
|
|
26
|
+
try {
|
|
27
|
+
return /^class(?:\s|\{)/u.test(Function.prototype.toString.call(value));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
22
33
|
function executionLogPath(path) {
|
|
23
34
|
const shard = (process.env["SUPERCOV_EXECUTION_LOG_SHARD"] ?? "host").replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
24
35
|
const suffix = `.${shard}.${process.pid}.jsonl`;
|
|
@@ -275,6 +286,13 @@ function wrapImportedMember(member, receiver, invoke) {
|
|
|
275
286
|
*/
|
|
276
287
|
export function wrapCapabilityCallbacks(value, mapping, depth = 0, seen = new WeakMap()) {
|
|
277
288
|
if (typeof value === "function") {
|
|
289
|
+
// A class is a nominal value as well as a callable capability. Replacing
|
|
290
|
+
// it with a Proxy changes strict constructor identity in registries such
|
|
291
|
+
// as Lexical, ORMs and dependency-injection containers. Static builder
|
|
292
|
+
// methods are supervised at the export boundary; classes passed through
|
|
293
|
+
// ordinary configuration must remain the exact original value.
|
|
294
|
+
if (isClassConstructor(value))
|
|
295
|
+
return value;
|
|
278
296
|
const cached = seen.get(value);
|
|
279
297
|
if (cached)
|
|
280
298
|
return cached;
|
|
@@ -320,6 +338,10 @@ export function wrapCapabilityCallbacks(value, mapping, depth = 0, seen = new We
|
|
|
320
338
|
export function wrapCapabilityObject(value, mapping) {
|
|
321
339
|
if ((!value || typeof value !== "object") && typeof value !== "function")
|
|
322
340
|
return value;
|
|
341
|
+
if (isClassConstructor(value)) {
|
|
342
|
+
patchBuilder(value);
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
323
345
|
const object = value;
|
|
324
346
|
const cached = capabilityProxies.get(object);
|
|
325
347
|
if (cached)
|
|
@@ -379,6 +401,10 @@ export function wrapCapabilityObject(value, mapping) {
|
|
|
379
401
|
export function wrapImportedCapability(value) {
|
|
380
402
|
if ((!value || typeof value !== "object") && typeof value !== "function")
|
|
381
403
|
return value;
|
|
404
|
+
if (isClassConstructor(value)) {
|
|
405
|
+
patchBuilder(value);
|
|
406
|
+
return value;
|
|
407
|
+
}
|
|
382
408
|
const object = value;
|
|
383
409
|
const cached = importedCapabilityProxies.get(object);
|
|
384
410
|
if (cached)
|
|
@@ -408,6 +434,10 @@ export function wrapImportedCapability(value) {
|
|
|
408
434
|
if (fixed.fixed)
|
|
409
435
|
return fixed.value;
|
|
410
436
|
const member = Reflect.get(target, property, target);
|
|
437
|
+
if (isClassConstructor(member)) {
|
|
438
|
+
patchBuilder(member);
|
|
439
|
+
return member;
|
|
440
|
+
}
|
|
411
441
|
if (typeof member !== "function")
|
|
412
442
|
return wrapImportedCapability(member);
|
|
413
443
|
return wrapImportedMember(member, target, invoke);
|
|
@@ -666,7 +666,7 @@ function wrapMatchers(matchers, path = "expect") {
|
|
|
666
666
|
}, (error) => {
|
|
667
667
|
if (phase && controller)
|
|
668
668
|
controller.finish(phase, error);
|
|
669
|
-
throw error;
|
|
669
|
+
throw coverageRuntime.cleanInstrumentationStack(error);
|
|
670
670
|
});
|
|
671
671
|
}
|
|
672
672
|
if (phase && controller)
|
|
@@ -676,7 +676,7 @@ function wrapMatchers(matchers, path = "expect") {
|
|
|
676
676
|
catch (error) {
|
|
677
677
|
if (phase && controller)
|
|
678
678
|
controller.finish(phase, error);
|
|
679
|
-
throw error;
|
|
679
|
+
throw coverageRuntime.cleanInstrumentationStack(error);
|
|
680
680
|
}
|
|
681
681
|
};
|
|
682
682
|
},
|
|
@@ -149,6 +149,7 @@ function createState() {
|
|
|
149
149
|
serverBuffers: /* @__PURE__ */ new Map(),
|
|
150
150
|
persistedServerRecords: /* @__PURE__ */ new Set(),
|
|
151
151
|
backgroundBuffers: /* @__PURE__ */ new Map(),
|
|
152
|
+
backgroundWriters: /* @__PURE__ */ new Map(),
|
|
152
153
|
backgroundSequence: 0,
|
|
153
154
|
runtimeSnapshots: false,
|
|
154
155
|
assertionPhases: /* @__PURE__ */ new Map(),
|
|
@@ -371,28 +372,12 @@ function flushAllBufferedServerEvidence() {
|
|
|
371
372
|
flushBufferedBackgroundEvidence(runId);
|
|
372
373
|
}
|
|
373
374
|
function flushBufferedBackgroundEvidence(runId) {
|
|
374
|
-
var _a8;
|
|
375
375
|
if (isBrowser)
|
|
376
376
|
return void 0;
|
|
377
377
|
const records = state.backgroundBuffers.get(runId);
|
|
378
378
|
if (!records || records.size === 0)
|
|
379
379
|
return void 0;
|
|
380
|
-
|
|
381
|
-
if (!fs)
|
|
382
|
-
return void 0;
|
|
383
|
-
const directory = backgroundEvidenceDirectory(runId);
|
|
384
|
-
const shard = (_a8 = process.env["SUPERCOV_EXECUTION_LOG_SHARD"]) != null ? _a8 : "process";
|
|
385
|
-
const writer = `${shard}-${process.pid}`;
|
|
386
|
-
const payload = [...records.values()].map((record) => JSON.stringify(record)).join("\n") + "\n";
|
|
387
|
-
try {
|
|
388
|
-
fs.mkdirSync(directory, { recursive: true });
|
|
389
|
-
const nextSequence = writeExclusiveBackgroundRecord(fs, runId, writer, state.backgroundSequence, payload);
|
|
390
|
-
state.backgroundSequence = nextSequence;
|
|
391
|
-
state.backgroundBuffers.delete(runId);
|
|
392
|
-
return backgroundEvidencePath(runId, `${writer}-${nextSequence - 1}`);
|
|
393
|
-
} catch (e) {
|
|
394
|
-
return void 0;
|
|
395
|
-
}
|
|
380
|
+
return state.backgroundWriters.get(runId);
|
|
396
381
|
}
|
|
397
382
|
function writeExclusiveBackgroundRecord(fs, runId, writer, initialSequence, payload) {
|
|
398
383
|
let sequence = initialSequence;
|
|
@@ -409,6 +394,30 @@ function writeExclusiveBackgroundRecord(fs, runId, writer, initialSequence, payl
|
|
|
409
394
|
}
|
|
410
395
|
throw Object.assign(new Error("Could not allocate a collision-free Supercov background evidence record"), { code: "SUPERCOV_BACKGROUND_COLLISION_LIMIT" });
|
|
411
396
|
}
|
|
397
|
+
function appendDurableBackgroundRecord(fs, runId, record) {
|
|
398
|
+
var _a8;
|
|
399
|
+
const records = state.backgroundBuffers.get(runId) != null ? state.backgroundBuffers.get(runId) : /* @__PURE__ */ new Map();
|
|
400
|
+
const key = serverRecordKey(record);
|
|
401
|
+
if (records.has(key))
|
|
402
|
+
return state.backgroundWriters.get(runId);
|
|
403
|
+
const directory = backgroundEvidenceDirectory(runId);
|
|
404
|
+
const payload = JSON.stringify(record) + "\n";
|
|
405
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
406
|
+
let path = state.backgroundWriters.get(runId);
|
|
407
|
+
if (!path) {
|
|
408
|
+
const shard = (_a8 = process.env["SUPERCOV_EXECUTION_LOG_SHARD"]) != null ? _a8 : "process";
|
|
409
|
+
const writer = `${shard}-${process.pid}`;
|
|
410
|
+
const nextSequence = writeExclusiveBackgroundRecord(fs, runId, writer, state.backgroundSequence, payload);
|
|
411
|
+
state.backgroundSequence = nextSequence;
|
|
412
|
+
path = backgroundEvidencePath(runId, `${writer}-${nextSequence - 1}`);
|
|
413
|
+
state.backgroundWriters.set(runId, path);
|
|
414
|
+
} else {
|
|
415
|
+
fs.appendFileSync(path, payload);
|
|
416
|
+
}
|
|
417
|
+
records.set(key, record);
|
|
418
|
+
state.backgroundBuffers.set(runId, records);
|
|
419
|
+
return path;
|
|
420
|
+
}
|
|
412
421
|
var _a7;
|
|
413
422
|
if (!isBrowser) {
|
|
414
423
|
const flushers = (_a7 = runtimeGlobal.__SUPERCOV_BUFFER_FLUSHERS__) != null ? _a7 : /* @__PURE__ */ new Set();
|
|
@@ -475,18 +484,19 @@ function appendServer(record) {
|
|
|
475
484
|
if (deduplicationKey && state.persistedServerRecords.has(deduplicationKey))
|
|
476
485
|
return;
|
|
477
486
|
if (!path) {
|
|
478
|
-
|
|
479
|
-
buffered.set(serverRecordKey(serialized), serialized);
|
|
480
|
-
state.backgroundBuffers.set(runId, buffered);
|
|
481
|
-
if (buffered.size >= 4096)
|
|
482
|
-
flushBufferedBackgroundEvidence(runId);
|
|
487
|
+
appendDurableBackgroundRecord(fs, runId, serialized);
|
|
483
488
|
return;
|
|
484
489
|
}
|
|
485
490
|
fs.mkdirSync(directory, { recursive: true });
|
|
486
491
|
fs.appendFileSync(path, JSON.stringify(serialized) + "\n");
|
|
487
492
|
if (deduplicationKey)
|
|
488
493
|
state.persistedServerRecords.add(deduplicationKey);
|
|
489
|
-
} catch (
|
|
494
|
+
} catch (cause) {
|
|
495
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
496
|
+
const failure = new Error(`Supercov could not persist coverage evidence for run ${runId}: ${detail}`);
|
|
497
|
+
failure.code = "SUPERCOV_EVIDENCE_TRANSPORT_FAILED";
|
|
498
|
+
failure.cause = cause;
|
|
499
|
+
throw failure;
|
|
490
500
|
}
|
|
491
501
|
}
|
|
492
502
|
function environmentRequestContext() {
|
|
@@ -542,6 +552,20 @@ function finishAssertionPhase(phase, error) {
|
|
|
542
552
|
if (error !== void 0)
|
|
543
553
|
phase.error = error instanceof Error ? error.message : String(error);
|
|
544
554
|
}
|
|
555
|
+
|
|
556
|
+
function cleanInstrumentationStack(error) {
|
|
557
|
+
if (!error || typeof error !== "object" || typeof error.stack !== "string")
|
|
558
|
+
return error;
|
|
559
|
+
const lines = error.stack.split("\n");
|
|
560
|
+
const visible = lines.filter((line, index) => index === 0 || !/[\\/]\.supercov[\\/](?:playwright|nodeTest|vitest|runtime|launchSupervisor|nodeAssert|nodeAssertStrict|nodeAssertAdapter|register|resolve-loader)\.(?:js|mjs)(?::|\))/u.test(line));
|
|
561
|
+
if (visible.length !== lines.length) {
|
|
562
|
+
try {
|
|
563
|
+
error.stack = visible.join("\n");
|
|
564
|
+
} catch (e) {
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return error;
|
|
568
|
+
}
|
|
545
569
|
function withNodeAssertionPhase(operation, source, callback) {
|
|
546
570
|
var _a8;
|
|
547
571
|
const context = currentRequestContext();
|
|
@@ -571,13 +595,13 @@ function withNodeAssertionPhase(operation, source, callback) {
|
|
|
571
595
|
return value;
|
|
572
596
|
}, (error) => {
|
|
573
597
|
finishAssertionPhase(phase, error);
|
|
574
|
-
throw error;
|
|
598
|
+
throw cleanInstrumentationStack(error);
|
|
575
599
|
});
|
|
576
600
|
finishAssertionPhase(phase);
|
|
577
601
|
return result;
|
|
578
602
|
} catch (error) {
|
|
579
603
|
finishAssertionPhase(phase, error);
|
|
580
|
-
throw error;
|
|
604
|
+
throw cleanInstrumentationStack(error);
|
|
581
605
|
}
|
|
582
606
|
}
|
|
583
607
|
function takeNodeAssertionPhases(scope) {
|
|
@@ -1021,6 +1045,7 @@ const directRuntimeApi = {
|
|
|
1021
1045
|
coverageCarrier,
|
|
1022
1046
|
coverageContextEnvironment,
|
|
1023
1047
|
coverageContextHeaders,
|
|
1048
|
+
cleanInstrumentationStack,
|
|
1024
1049
|
coverageHit,
|
|
1025
1050
|
coverageHitV2,
|
|
1026
1051
|
coverageSnapshot,
|
|
@@ -1067,6 +1092,7 @@ export {
|
|
|
1067
1092
|
coverageCarrier,
|
|
1068
1093
|
coverageContextEnvironment,
|
|
1069
1094
|
coverageContextHeaders,
|
|
1095
|
+
cleanInstrumentationStack,
|
|
1070
1096
|
coverageHit,
|
|
1071
1097
|
coverageHitV2,
|
|
1072
1098
|
coverageSnapshot,
|