supercov 2.0.0 → 3.0.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.
Files changed (37) hide show
  1. package/README.md +52 -22
  2. package/docs/agent-loop.md +8 -13
  3. package/docs/assertions.md +161 -75
  4. package/docs/cli.md +111 -59
  5. package/docs/coverage-model.md +5 -6
  6. package/docs/evidence.md +0 -11
  7. package/docs/getting-started.md +1 -1
  8. package/docs/performance.md +6 -14
  9. package/docs/quality.md +1 -1
  10. package/docs/security.md +43 -12
  11. package/docs/supported-suites.md +44 -48
  12. package/docs/troubleshooting.md +12 -5
  13. package/docs/workspace-isolation.md +23 -2
  14. package/package.json +36 -21
  15. package/runtime/javascript/atomic.mjs +46 -2
  16. package/runtime/javascript/ava.mjs +82 -0
  17. package/runtime/javascript/bootstrap.cjs +43 -0
  18. package/runtime/javascript/jest.cjs +52 -15
  19. package/runtime/javascript/jest.config.mjs +9 -1
  20. package/runtime/javascript/jestReporter.mjs +5 -6
  21. package/runtime/javascript/launchSupervisor.mjs +127 -15
  22. package/runtime/javascript/mocha.mjs +180 -0
  23. package/runtime/javascript/nodeTest.mjs +57 -3
  24. package/runtime/javascript/playwright.mjs +241 -32
  25. package/runtime/javascript/provenance.mjs +1 -1
  26. package/runtime/javascript/register.mjs +290 -2
  27. package/runtime/javascript/resolve-loader.mjs +40 -1
  28. package/runtime/javascript/runnerEvidence.mjs +3 -5
  29. package/runtime/javascript/runtime.mjs +551 -61
  30. package/runtime/javascript/tap.mjs +130 -0
  31. package/runtime/javascript/transport.mjs +7 -2
  32. package/runtime/javascript/vitest.mjs +5 -8
  33. package/runtime/javascript/vitestReporter.mjs +3 -10
  34. package/docs/assertion-agent.md +0 -150
  35. package/docs/assertion-evidence.md +0 -103
  36. package/docs/assertion-maps.md +0 -316
  37. package/schemas/assertions.schema.json +0 -276
package/docs/cli.md CHANGED
@@ -17,8 +17,11 @@ npx supercov --help
17
17
  | Read the newest run | `npx supercov runs latest` |
18
18
  | Find useful gaps | `npx supercov runs latest gaps` |
19
19
  | Inspect one file | `npx supercov runs latest file <path>` |
20
- | List assertions and their status | `npx supercov runs latest assertions` |
21
- | Inspect one assertion and its flows | `npx supercov runs latest assertion <id>` |
20
+ | See how much executed code the tests assert | `npx supercov runs latest assertions` |
21
+ | Assess it with Jev | `npx supercov runs latest assertions assess` |
22
+ | Find what is not asserted in one file | `npx supercov runs latest assertions <path>` |
23
+ | See each test's answer for one statement | `npx supercov runs latest assertions <path>:<line>` |
24
+ | See what one test is judged to catch | `npx supercov runs latest assertions --test <name>` |
22
25
  | Read matching current source code | `npx supercov runs latest source <path>` |
23
26
  | Compare two runs | `npx supercov diff <older> <newer>` |
24
27
  | Open an interactive report | `npx supercov report` |
@@ -36,17 +39,17 @@ npx supercov --help
36
39
  ## Assess source quality
37
40
 
38
41
  ```sh supercov-example
39
- supercov quality # this repository
40
- supercov quality src/ # one directory
41
- supercov quality gaps # only files something fired on
42
- supercov quality file src/a.ts # one file, every check
43
- supercov quality scope # which files are assessed, and why
44
- supercov quality snapshots # saved assessments
45
- supercov quality diff <older> <newer>
46
- supercov security # twelve security checks, every file
47
- supercov security gaps # only files something fired on
48
- supercov security patch --base origin/main
49
- supercov security --run latest # flagged files no test exercises
42
+ npx supercov quality # this repository
43
+ npx supercov quality src/ # one directory
44
+ npx supercov quality gaps # only files something fired on
45
+ npx supercov quality file src/a.ts # one file, every check
46
+ npx supercov quality scope # which files are assessed, and why
47
+ npx supercov quality snapshots # saved assessments
48
+ npx supercov quality diff <older> <newer>
49
+ npx supercov security # twelve security checks, every file
50
+ npx supercov security gaps # only files something fired on
51
+ npx supercov security patch --base origin/main
52
+ npx supercov security --run latest # flagged files no test exercises
50
53
  ```
51
54
 
52
55
  With no argument the subject is the repository you are standing in. Every
@@ -74,9 +77,9 @@ number is worth and which files get assessed.
74
77
  ## Review what a change introduced
75
78
 
76
79
  ```sh supercov-example
77
- supercov quality patch
78
- supercov quality patch --base origin/main
79
- supercov quality patch --base origin/main --annotate github --run latest
80
+ npx supercov quality patch
81
+ npx supercov quality patch --base origin/main
82
+ npx supercov quality patch --base origin/main --annotate github --run latest
80
83
  ```
81
84
 
82
85
  The same twelve properties asked of a change, plus six risk checks that only
@@ -146,8 +149,7 @@ npx supercov runs <run-id> [query] [options]
146
149
  | `kinds` | Group coverage by test level, such as unit or E2E |
147
150
  | `runners` | Group coverage by test runner |
148
151
  | `scope` | Review included, excluded, and ambiguous source files |
149
- | `assertions` | List assertions, including sites without flows, with freshness and execution status |
150
- | `assertion <id>` | Inspect one assertion and its authored flows |
152
+ | `assertions` | Read the share of executed statements the tests assert, and the ones they do not; `assertions assess` works it out |
151
153
  | `source <path>` | Read matching current project source with line numbers |
152
154
  | `minimize` | Find a small test subset that preserves a coverage target |
153
155
 
@@ -169,46 +171,34 @@ npx supercov runs latest file --help
169
171
  npx supercov runs latest assertions --help
170
172
  ```
171
173
 
172
- Assertion queries read the run-owned map. `source <path>` reads the matching current
173
- file directly. It prints source code with line numbers, preserving indentation;
174
- add `--json` only when you want structured `{line, text}` items. `--offset` is
175
- zero-based and `--limit` controls the number of source lines. Source and assertion
176
- investigation require current files that match the run. Rerun the suite after
177
- source changes to inherit the map into a new run.
174
+ `source <path>` reads the matching current file directly. It prints source code
175
+ with line numbers, preserving indentation; add `--json` only when you want
176
+ structured `{line, text}` items. `--offset` is zero-based and `--limit` controls
177
+ the number of source lines. The file must still match the run; rerun the suite
178
+ after source changes.
178
179
 
179
180
  ### Assertion coverage
180
181
 
181
- The regular run summary includes assertion coverage when a map has been
182
- assessed. JSON reports expose it under `data.assertionCoverage`. Start with
183
- [Understanding assertion coverage](assertions.md), or use these commands to
184
- inspect and check a map:
185
-
186
182
  ```sh supercov-example
187
- npx supercov runs <run-id> assertions --needs-attention
188
- npx supercov runs <run-id> assertion <assertion-id>
189
- npx supercov runs <run-id> assertions report --view statements --file src/shipping.js
190
- npx supercov runs <run-id> assertions report --view excludedStatements
191
- npx supercov runs <run-id> assertions validate --json
192
- npx supercov runs <run-id> assertions check --require-mappings
183
+ npx supercov runs <run-id> assertions assess --dry-run
184
+ npx supercov runs <run-id> assertions assess
185
+ npx supercov runs <run-id> assertions
186
+ npx supercov runs <run-id> assertions --all --json
193
187
  ```
194
188
 
195
- Edit the file shown by `assertions`. Validation returns `expectedBasis` tokens;
196
- after examining a flow, save its token in the map before running `check`.
197
- `--require-mappings` requires explanations for recognized assertions observed
198
- passing. Add `--require-observed` when every mapped site and selector should have
199
- passing evidence, or `--min <percentage>` for a chosen target.
200
-
201
- To inspect one large flow, add `--flow <flow-id> --view nodes` or `--view edges`
202
- to the assertion detail command. `--compact` omits repeated source text from the
203
- report. Follow the printed next-page command or JSON `pagination.nextOffset`.
204
- Validation supports `--view flows`, `--view changes` and `--view errors` for large
205
- maps. The [map reference](assertion-maps.md) describes all fields and gates.
189
+ `assertions assess` asks Jev, test by test, whether each statement the test ran
190
+ would make it fail if changed, and saves the result to the run; it needs
191
+ `TYPESAFE_API_KEY` for questions not answered before. `assertions` reads the
192
+ saved result without the network. The regular run summary shows the same share,
193
+ and JSON reports expose it under `data.assertionCoverage`. See
194
+ [Assertion coverage](assertions.md) for what counts as asserted and how answers
195
+ are reused across runs.
206
196
 
207
197
  ## Fail CI below a coverage floor
208
198
 
209
199
  ```sh supercov-example
210
- supercov runs check --min-lines 90 --min-branches 80 --min-mcdc 80
211
- supercov runs check --min-lines 100 --per-file --json
200
+ npx supercov runs check --min-lines 90 --min-branches 80 --min-mcdc 80
201
+ npx supercov runs check --min-lines 100 --per-file --json
212
202
  ```
213
203
 
214
204
  `check` reads a recorded run; it never runs tests again. Give a floor per metric
@@ -231,16 +221,11 @@ rather than a pass or a failure:
231
221
  - a requested metric left obligations unmeasured, so no exact judgement exists
232
222
  - a requested metric is not recorded by the language adapter
233
223
 
234
- Assertion coverage keeps its own check. Whether a test *examines* what it
235
- executes is a different question from whether a line ran, and
236
- `runs <id> assertions check` carries the freshness and acknowledgement rules
237
- that answer needs.
238
-
239
224
  ## Check the lines a change touches
240
225
 
241
226
  ```sh supercov-example
242
- supercov runs patch --base origin/main --min-lines 100
243
- supercov runs patch --base origin/main --annotate github
227
+ npx supercov runs patch --base origin/main --min-lines 100
228
+ npx supercov runs patch --base origin/main --annotate github
244
229
  ```
245
230
 
246
231
  `patch` answers whether the lines this change added or modified are tested. It
@@ -267,9 +252,9 @@ needs no token and posts no comment.
267
252
  ## Export for other tools
268
253
 
269
254
  ```sh supercov-example
270
- supercov runs report --format lcov --output coverage/lcov.info
271
- supercov runs report --format cobertura --output coverage/cobertura.xml
272
- supercov runs report --format html --output coverage/report
255
+ npx supercov runs report --format lcov --output coverage/lcov.info
256
+ npx supercov runs report --format cobertura --output coverage/cobertura.xml
257
+ npx supercov runs report --format html --output coverage/report
273
258
  ```
274
259
 
275
260
  Both are written from the same view `check` and `patch` read, so a viewer,
@@ -401,11 +386,72 @@ neither do comments, blank lines or trailing whitespace. A test that did not
401
386
  pass in the run is listed regardless.
402
387
 
403
388
  `--names` prints one affected test name per line and `--files` one test file
404
- per line, for a runner's filter. A dependency, lockfile, configuration or
389
+ per line, for a runner's filter. A Vitest, Playwright or node:test test inside
390
+ `describe` blocks is printed as the blocks and the test joined by spaces
391
+ (`small charges`), which is what `vitest -t`, `--grep` and
392
+ `--test-name-pattern` match. A dependency, lockfile, configuration or
405
393
  toolchain change affects every test and is reported as such. A source file
406
394
  added since the run is outside every test's record; the working-tree check
407
395
  says so, and the suite should run in full.
408
396
 
397
+ ### Which affected tests check the change
398
+
399
+ When the run has an [assertion assessment](assertions.md), `tests affected`
400
+ also says, for each affected test, whether it was judged to catch a change to
401
+ the statements that changed in code it ran, and lists those tests first. A run
402
+ without an assessment answers from coverage alone, as above.
403
+
404
+ ```sh supercov
405
+ npx supercov runs latest assertions assess # once, on the run
406
+ # ... edit code ...
407
+ npx supercov runs latest assertions assess --changed # ask every test that ran the changed code
408
+ npx supercov runs latest tests affected --names --ran-changed
409
+ ```
410
+
411
+ The assessment stops asking about a statement once one test catches it, so on
412
+ its own it knows only some tests' answers. `assess --changed` asks, for the
413
+ statements in code changed since the run, every test that ran them: typically
414
+ a few requests per commit. After it, `--ran-changed` leaves out the affected
415
+ tests that ran the changed declaration but none of the changed statements, and
416
+ `--asserting` also those judged to catch no change to them. Tests whose own
417
+ file changed or that did not pass stay, and so does any test never asked. With
418
+ an assessment or without, `--names` and `--files` list first the tests whose
419
+ own file or name shares words with a changed file (`test_receivebuffer.py` for
420
+ `_receivebuffer.py`), the verdicts breaking ties.
421
+
422
+ Measured by applying random changes and running the suite:
423
+
424
+ | Project | Breaking changes (failing tests) | Coverage's set | `--ran-changed` | `--asserting` | A failing test in the first three |
425
+ | --- | --- | --- | --- | --- | --- |
426
+ | h11 (Python, 78 tests) | 23 (153) | all 153 in 28 tests | all 153 in 23.5 | 141 in 19.9 | 18 of 23 |
427
+ | go-version (Go, 31 tests) | 36 (140) | all 140 in 10.1 | all 140 in 9.8 | 133 in 9.4 | 32 of 36 |
428
+
429
+ `--ran-changed` never dropped a failing test; `--asserting` dropped 5 to 8% of
430
+ them. `assess --changed` cost about a tenth of a cent per change.
431
+
432
+ Measured on real history: at each of a project's recent commits, the source
433
+ the commit replaced was put back -- someone reintroducing what it fixed -- and
434
+ the commit's own tests run against it:
435
+
436
+ | Project | Commits (breaking) | Failing tests | Kept by coverage's set | Kept by `--asserting` | Tests selected (coverage / `--asserting`) of all |
437
+ | --- | --- | --- | --- | --- | --- |
438
+ | h11 (Python) | 60 (13) | 27 | 27 | 27 | 25.2 / 24.9 of 75 |
439
+ | go-version (Go) | 24 (2) | 3 | 3 | 3 | 15.2 / 14.1 of 25 |
440
+ | dry-inflector (Ruby) | 12 (7) | 26 | 26 | 26 | 813 / 718 of 1,109 |
441
+ | uuid (TypeScript, node:test) | 4 (3) | 6 | 6 | 6 | 13.2 / 11.5 of 64 |
442
+ | dtolnay/semver (Rust) | 20 (0) | 0 | -- | -- | 27.9 / 25.9 of 38 |
443
+ | commons-cli (Java) | 15 (7) | 12 | 12 | 12 | 159 / 143 of 985 |
444
+
445
+ No failing test was left out of the set to run. On real commits the
446
+ assessment narrows little -- Jev judges most tests that ran a changed line to
447
+ catch the change -- and its verdicts alone ranked a failing test in the first
448
+ three for only 6 of 32 breaking commits. Ranking by name, with the verdicts
449
+ breaking ties, put one there for 10 of those 32, and for 15 of 25 breaking
450
+ commits in four projects it was not designed on (hashie, Masterminds/semver,
451
+ itsdangerous, java-classmate), where file order managed 11; without an
452
+ assessment, name alone managed 8 and 12. After a project's first
453
+ assessment, each commit cost under a cent.
454
+
409
455
  ## Combine shards
410
456
 
411
457
  ```sh supercov
@@ -461,6 +507,7 @@ terminal or offline environment after the package has been downloaded.
461
507
  | --- | --- |
462
508
  | `SUPERCOV_SOURCE_ROOTS` | Comma-separated directories or files that hold your own code, in any language; everything else is left out |
463
509
  | `SUPERCOV_TEST_KIND` | Label the wrapped command as a test level such as `unit` or `e2e` |
510
+ | `SUPERCOV_BUILD_COMMAND` | The build a JavaScript project's tests need, as words or a JSON array, such as `yarn workspace web build` in a monorepo whose root `build` builds every package. Without it Supercov runs the project's `build` script, through the package manager that started the tests, when the tests need built output |
464
511
 
465
512
  Examples:
466
513
 
@@ -477,3 +524,8 @@ SUPERCOV_TEST_KIND=e2e npx supercov -- npx playwright test
477
524
  | Wrapped command's code | The test command failed and Supercov preserved its status |
478
525
  | `1` | A valid measurement failed a policy you set, such as a coverage floor |
479
526
  | `2` | Supercov could not complete the request, or the evidence cannot answer it |
527
+
528
+ `quality` and `security` exit `0` for a complete report in which some files
529
+ could not be assessed, such as a file larger than the model accepts; each is
530
+ listed in the report with its reason, and stderr says how many. They exit `2`
531
+ when no file could be assessed.
@@ -150,9 +150,8 @@ or generated output merely to make a warning disappear.
150
150
 
151
151
  ## Assertion percentage
152
152
 
153
- When a run has an `assertions.json` map, its regular summary also shows
154
- agent-assessed assertion coverage: credited measured statements divided by all
155
- measured statements. This row always describes the whole archived run,
156
- independently of `--filter`, `--kind` and `--runner`. It is separate from the
157
- structural metrics above. See [assertion maps](assertion-maps.md) for credit,
158
- freshness and incomplete-map rules.
153
+ When a run has been assessed with `assertions assess`, its regular summary also
154
+ shows assertion coverage: executed statements a test is judged to catch
155
+ breaking, divided by all assessed statements. This row always describes the
156
+ whole run, independently of `--filter`, `--kind` and `--runner`, and is separate
157
+ from the structural metrics above. See [assertion coverage](assertions.md).
package/docs/evidence.md CHANGED
@@ -134,14 +134,3 @@ SUPERCOV_TEST_KIND=integration npx supercov -- npm test
134
134
 
135
135
  Supported values are `unit`, `component`, `integration` and `e2e`. Use separate
136
136
  runs when different suites need different classifications.
137
-
138
- ## When assertion evidence is missing
139
-
140
- A test can make assertions without executing measured application code. It
141
- might check static data or a dependency, or use work performed in shared setup.
142
- Missing attribution across an asynchronous boundary can also leave a gap.
143
-
144
- The assertion detail explains why a mapped node did or did not receive credit.
145
- Use [Investigating assertion evidence](assertion-evidence.md) to distinguish
146
- these cases. The report's runtime action-phase counts are separate from the
147
- assertion map; zero action-phase lines does not mean zero asserted statements.
@@ -139,7 +139,7 @@ if anything is missing.
139
139
  - for Go, Go 1.22 or newer;
140
140
  - for Java and Kotlin, JDK 17 or newer, with Maven or Gradle;
141
141
  - for Python, CPython 3.9 or newer with pytest or unittest;
142
- - for Ruby, Ruby 3.4 or newer with RSpec, Minitest, test-unit or Cucumber (3.3 measures lines, methods and simple branches only).
142
+ - for Ruby, Ruby 3.3 or newer with RSpec, Minitest, test-unit or Cucumber.
143
143
 
144
144
  The CLI is a native binary. `npx supercov` picks the build for your operating
145
145
  system and architecture, and nothing is compiled on install; the same binary is
@@ -130,18 +130,10 @@ Use `--dry-run` to preview cleanup. Keep enough run history for active reviews
130
130
  and automation; remove the cache only when reclaiming space matters more than a
131
131
  faster next run.
132
132
 
133
- ## Keep assertion investigation fast
133
+ ## Keep assertion assessments fast and cheap
134
134
 
135
- Save `assertions.json` and query the run to see the updated score. Supercov does
136
- not rerun tests or call a model to calculate the report. Repeated queries reuse
137
- the previous assessment while checking that your source still matches the run.
138
- Editing the map automatically refreshes that assessment.
139
-
140
- For large maps, ask for a short page instead of the whole graph. Within one
141
- flow, `--view nodes` or `--view edges` pages the details; `--compact` omits repeated
142
- source text while keeping locations and credit reasons. See
143
- [Investigating assertion evidence](assertion-evidence.md#read-a-large-flow).
144
-
145
- The disposable `assertions.report.cache.json` and `assertions.summary.cache.json`
146
- files live beside the map. A missing or damaged cache is rebuilt. Removing it affects the next query's speed, not
147
- your saved explanations.
135
+ `assertions assess` asks only questions it has not answered before; answers live
136
+ in `.supercov/assertions/` and are reused across runs whenever the question is
137
+ the same. `--workers N` raises the requests in flight (default 8) for a faster,
138
+ slightly costlier pass. `--dry-run` prints the estimate without sending
139
+ anything.
package/docs/quality.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Understanding quality
2
2
 
3
- `supercov quality` tells you what is in your code: twelve named properties,
3
+ `npx supercov quality` tells you what is in your code: twelve named properties,
4
4
  checked file by file, so a score is never a number you have to take on faith.
5
5
 
6
6
  Judgments come from [Jev](https://typesafe.ai), which answers typed questions
package/docs/security.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Security surface
2
2
 
3
- `supercov security` tells you what security-relevant surface each file shows:
3
+ `npx supercov security` tells you what security-relevant surface each file shows:
4
4
  twelve named checks, asked file by file, each mapped to the weakness classes it
5
5
  stands for. There is no score. A file is clean, or it names what fired.
6
6
 
7
- Judgments come from [Jev](https://typesafe.ai), the same way `supercov quality`
7
+ Judgments come from [Jev](https://typesafe.ai), the same way `npx supercov quality`
8
8
  gets them. Set `TYPESAFE_API_KEY`, and optionally another provider, as described
9
9
  in [quality](quality.md); reading a saved assessment never needs a key.
10
10
 
@@ -50,16 +50,47 @@ attempt measured on the labelled corpus fired on unguarded handlers the
50
50
  application leaves open by design as often as on the ones it should not.
51
51
 
52
52
 
53
- Measured with this command on fifteen held-out repositories of a
54
- 140-repository labelled corpus, whole repositories, at finding level: F1
55
- 0.47 with 54% precision and 41% recall, where Semgrep scores 0.14, the
56
- general agentic LLM scanners 0.50 to 0.60, and the two leaders 0.76 and
57
- 0.77, at about two cents per repository against 30 cents to 4 dollars for
58
- the agentic scanners. Injection, secrets, path, redirect and mass
59
- assignment are found at 70 to 90% recall; authorisation and authentication
60
- at 10%, because the guard that decides them lives in another file. A file
61
- too large for one request is read in windows at declaration boundaries.
62
-
53
+ Measured with 2.0.1 on the 72 held-out repositories of RealVuln, a
54
+ 140-repository labelled corpus, scored by its own rule: F1 0.42 with 42%
55
+ precision and 43% recall, in a median of 10 seconds and about 5 cents a
56
+ repository. The strongest agent-based scanners score 0.69 to 0.71 but take
57
+ 5 to 9 minutes and about $4 a repository; Semgrep's default rules score 0.11.
58
+ Redirects, injection, unsafe code execution and paths taken from a request
59
+ are found at 79 to 89% recall; authorisation (11%) and authentication (24%)
60
+ are the weakest, because the guard that decides them lives in another file.
61
+ Every scanner's results are at
62
+ [supercov.com/docs/security-benchmark](https://supercov.com/docs/security-benchmark).
63
+ A file too large for one request is read in windows at declaration
64
+ boundaries, or at line boundaries where Supercov has no parser for it, such as
65
+ a template.
66
+
67
+ ## What is read
68
+
69
+ Security reads every source file, template and configuration file that the
70
+ conventions do not exclude, including files under no recognised source root,
71
+ because code a project did not declare is deployed as often as not.
72
+ `npx supercov security scope` lists exactly the files a run reads and why the
73
+ rest were left out. Three kinds are left out because they are not the code
74
+ under review:
75
+
76
+ - **Tests and generated output**, as for `quality`.
77
+ - **Vendored and minified JavaScript.** A minified file anywhere, and in a
78
+ served or vendor directory (`static/`, `public/`, `assets/`, `plugins/`,
79
+ `vendor/`, `lib/`) a file with a licence banner or a well-known library's
80
+ name, such as `jquery.flot.js` or `swagger-ui-bundle.js`.
81
+ - **JSON data.** A JSON file is read when its name or directory says it
82
+ configures something (`config.json`, `proxy.conf.json`, `appsettings.json`,
83
+ `secrets.json`, anything under `config/`), and not when it holds data such
84
+ as translations, schema snapshots or scanner output.
85
+
86
+ A directory called `target/` is build output only when a build that writes
87
+ there (Cargo, Maven, Gradle, sbt, Leiningen) is declared beside it; otherwise
88
+ it is read like any other. Name a file directly to read it whatever the scope
89
+ says: `npx supercov security static/js/app.min.js`.
90
+
91
+ A run that could not assess a few files still exits 0: each one is in the
92
+ report with its reason, and stderr says how many. It exits 2 only when no
93
+ file could be assessed.
63
94
  ## Across files
64
95
 
65
96
  The second pass also labels every function of every file, on the same
@@ -50,9 +50,11 @@ Supercov reports the level it actually observed. It does not guess.
50
50
  | --- | --- |
51
51
  | Playwright | Exact per test, worker, retry, outcome, action, and assertion phase |
52
52
  | Vitest | Exact per test, with setup execution kept separate; Browser Mode included |
53
- | Jest | Exact per test, including parameterized tests, with the user's own configuration, setup files and reporters kept; passing `expect` occurrences are identified for assertion maps |
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
- | AVA and Mocha | Aggregate structural coverage |
55
+ | Mocha | Exact per test and retry, `--parallel` included, with setup execution kept separate |
56
+ | AVA | Exact per test, concurrent tests included, in worker threads or child processes |
57
+ | node-tap 15 and newer | Exact per subtest, nested and parallel (`t.jobs`) subtests included |
56
58
  | Other Node-based runners | Aggregate when their processes remain visible to Supercov |
57
59
  | Browser component runners without an adapter | Aggregate structural coverage |
58
60
 
@@ -81,9 +83,6 @@ obligations. `{value}` is reached exactly when the tree is, and
81
83
  `onClick={() => save()}` is measured where the handler is called rather than
82
84
  where it is created.
83
85
 
84
- `expect.element(...)`, `expect.soft(...)` and `expect.poll(...)` are recognised
85
- as assertions, so their passing occurrences are available to assertion maps.
86
-
87
86
  ### React and React Native
88
87
 
89
88
  React components can use Testing Library with Vitest/jsdom, Vitest Browser Mode,
@@ -93,11 +92,10 @@ it does not measure Hermes, native modules, simulator/device execution, Detox or
93
92
  Maestro. React SSR hydration is exercised in jsdom and Chromium; this does not
94
93
  establish Next.js Server Components, streaming SSR or server actions.
95
94
 
96
- An assertion map can explain which displayed value, accessible name, disabled
97
- state or error message a test checks. JSX expression coverage and a passing
98
- assertion are evidence for reviewing that explanation, not automatic semantic
99
- proof. A button being rendered does not establish that its disabled state was
100
- checked. Keep your existing runner and matchers.
95
+ Assertion coverage treats a JSX expression as a statement: it asks whether a
96
+ test would notice the displayed value, accessible name, disabled state or error
97
+ message becoming undefined. A button being rendered does not establish that its
98
+ disabled state was checked. Keep your existing runner and matchers.
101
99
 
102
100
  The [React verification example](https://github.com/supercorp-ai/supercov/tree/main/examples/react-verification)
103
101
  shows a fully executed checkout whose weak tests accept four UI regressions,
@@ -118,11 +116,6 @@ Supercov runs the project's build inside the isolated copy before testing.
118
116
  Keep using your normal test and build commands. Instrumentation does not
119
117
  require changing the project's TypeScript settings.
120
118
 
121
- For assertion maps, Node, Vitest, Jest and Playwright's Node-side assertions
122
- supply supported passing-occurrence evidence. Custom assertion wrappers and
123
- browser-side checks can have additional observation limits. See
124
- [Assertion evidence](assertion-evidence.md) before interpreting a missing occurrence.
125
-
126
119
  ### Browsers, servers, and child processes
127
120
 
128
121
  Playwright support includes Chromium, Firefox, and WebKit, along with pages,
@@ -169,6 +162,19 @@ modules. Statements, functions, branches, boolean decisions, loops and error
169
162
  propagation are measured. Const contexts and macro expansions remain visible
170
163
  with explicit measurement limitations.
171
164
 
165
+ Test code is not measured, as cargo-llvm-cov and cargo-tarpaulin leave it out:
166
+ files under a `tests`, `examples` or `benches` directory, files named
167
+ `tests.rs`, `*_tests.rs` or `*-tests.rs`, everything a test target or a
168
+ `#[cfg(test)]` module declares, and items marked `#[cfg(test)]`, `#[test]` or
169
+ `#[bench]`. Its assertions still mark which evidence a passing assertion
170
+ witnessed.
171
+
172
+ Neither is code the compiler never builds for the target: a `#[cfg(windows)]`
173
+ function on Linux, or one behind `#[cfg(not(target_os = "macos"))]` on a Mac,
174
+ is not counted. Only predicates the target settles (`unix`, `windows`,
175
+ `target_*`) are decided; an item behind a feature or a custom cfg is counted
176
+ either way.
177
+
172
178
  Use the repository's normal flags after the wrapped command:
173
179
 
174
180
  ```sh
@@ -200,7 +206,8 @@ comprehensions, short-circuit operators, `match` cases and exception paths.
200
206
  Child interpreters, threads and thread pools can retain the calling test's
201
207
  identity. The report also distinguishes execution before a passing assertion
202
208
  from later execution. These phase records alone do not prove which values the
203
- assertion checks.
209
+ assertion checks; [assertion coverage](assertions.md) measures which executed
210
+ statements the pytest and unittest tests would catch breaking.
204
211
 
205
212
  Interpreters launched with `-I`, `-E` or `-S` ignore the required startup hook
206
213
  and are not measured. Code compiled from strings at runtime has no source
@@ -222,23 +229,21 @@ npx supercov -- python -m unittest
222
229
  | Runner | Attribution | Current requirement |
223
230
  | --- | --- | --- |
224
231
  | RSpec | Exact example and before/example/after phase identity | Ruby 3.4 or newer for full measurement; run with `npx supercov -- rspec` or `bundle exec rspec` |
225
- | Minitest (including Minitest::Spec and ActiveSupport::TestCase) | Exact test and setup/test/teardown identity; a test's coverage is a lower bound (see below); skips recorded | `ruby -Itest ...`, `rake test`, `rails test` |
232
+ | Minitest (including Minitest::Spec and ActiveSupport::TestCase) | Exact test and setup/test/teardown identity; skips recorded | `ruby -Itest ...`, `rake test`, `rails test` |
226
233
  | test-unit | Exact test and setup/test/teardown identity; omissions and pendings recorded | `ruby -Itest ...`, `rake test` |
227
234
  | parallel_tests, Rails process workers | Exact per worker process | Workers inherit the run through `RUBYOPT`; verified on a Rails app with bootsnap, Zeitwerk and two forked workers |
228
235
  | Thread-parallel Minitest (`parallelize_me!`, `parallelize(with: :threads)`) | Probe observations exact per test; line, method and simple-branch observations made while phases overlapped go to the run, declared | |
229
236
  | Cucumber | Exact scenario identity (`features/x.feature:LINE`), hook steps as setup/teardown | `cucumber`, `bundle exec cucumber` |
230
237
 
231
- Ruby reports a line the first time it executes and never again, which is what
232
- makes collecting coverage cheap enough to leave on. So a test is credited with
233
- the lines it was first to reach, and a later test running the same lines is
234
- credited with none of them: what a test is credited with is really its own, and
235
- what it is not credited with is not evidence it did not run the code.
236
-
237
- Totals are unaffected — every line is credited to exactly one test. What this
238
- changes is per-test reporting: `supercov runs <id> test <name>` gives its
239
- numbers as "at least", and `tests affected` reports a test whose own record
240
- cannot settle the question as **undetermined** rather than unaffected, so
241
- `--names` includes it in the set to run.
238
+ Every test is credited with every line it runs: each statement of an
239
+ instrumented file carries a probe. Code Supercov cannot instrument -- a file
240
+ it could not compile with its probes, or a `Ractor.new` block, where no probe
241
+ can run -- falls back to Ruby's one-shot line coverage, which reports a line
242
+ the first time it executes and never again. There a test is credited with the
243
+ lines it was first to reach; what it is not credited with is not evidence it
244
+ did not run the code. The run says so, per-test numbers there read "at least",
245
+ and `tests affected` reports a test whose own record cannot settle the
246
+ question as **undetermined**, so `--names` includes it in the set to run.
242
247
 
243
248
  Your project runs in place with its own interpreter and bundle. Supercov loads
244
249
  through `RUBYOPT`; application files on disk and their backtrace line numbers
@@ -246,11 +251,9 @@ stay unchanged. RSpec, Minitest and test-unit assertions can identify execution
246
251
  before a passing assertion. That timing evidence alone does not show which
247
252
  values the assertion checks.
248
253
 
249
- Ruby 3.4 and newer support statement, method, branch and MC/DC measurement,
254
+ Ruby 3.3 and newer support statement, method, branch and MC/DC measurement,
250
255
  including loops, iterator blocks, short-circuit operators, pattern matching,
251
- optional calls and exception paths. Ruby 3.3 supplies Ruby's own line, method
252
- and branch coverage; obligations requiring additional instrumentation are
253
- reported as measurement limits.
256
+ optional calls and exception paths.
254
257
 
255
258
  Some constructs have narrower coverage. Code in a non-main Ractor keeps line
256
259
  coverage but may lack other observations. Certain nested-return expressions
@@ -300,7 +303,7 @@ which costs whatever its parallelism was worth — on sixteen I/O-bound JUnit
300
303
  tests across fifteen cores, 3.6x. Either way the coverage totals are the same;
301
304
  what changes is whether a line can be traced to the test that ran it.
302
305
 
303
- `supercov runs latest` reports which you got, under `Attribution`.
306
+ `npx supercov runs latest` reports which you got, under `Attribution`.
304
307
 
305
308
  ## Go
306
309
 
@@ -332,7 +335,7 @@ measured the same way: `go test` runs both, and what they reach is real
332
335
  coverage no test can claim.
333
336
 
334
337
  Such a test is still recorded as having run, with its outcome, and credited
335
- with no coverage. `supercov runs <id> test <name>` says that rather than
338
+ with no coverage. `npx supercov runs <id> test <name>` says that rather than
336
339
  reporting zeroes, coverage percentages leave it out, and `tests affected` lists
337
340
  it as **undetermined**: nothing can say a change missed it, so `--names`
338
341
  includes it in the set to run. A package where every test calls `t.Parallel()`
@@ -347,10 +350,6 @@ no way to run code on `os.Exit`, and a `TestMain` need not reach the `m.Run()`
347
350
  call Supercov wraps — `goleak.VerifyTestMain(m)` runs the suite and exits
348
351
  itself. Without periodic writes such a run recorded nothing at all.
349
352
 
350
- For assertion maps, `t.Error`, `t.Errorf`, `t.Fatal`, `t.Fatalf` and testify's
351
- `assert` and `require` are inventoried. A Go test states its claim with an `if`
352
- and reports the violation, so the report is the site.
353
-
354
353
  A repository with several modules works either way it is laid out. A directory
355
354
  with a `go.mod` of its own that no `go.work` names is a different module, and
356
355
  `go test ./...` walks past it, so Supercov leaves it alone. A `go.work`
@@ -372,7 +371,7 @@ npx supercov -- go test ./core/... ./app/...
372
371
  | Kotest | Exact per test, under the names Kotest itself reports | — |
373
372
  | Spock | Exact per feature, under the names Spock itself reports | — |
374
373
  | TestNG | Exact per test, each data-provider invocation its own | — |
375
- | JUnit 4 alone | Exact per test, through Vintage — see below | Maven |
374
+ | JUnit 4 alone | Exact per test, through Vintage — see below | Maven or Gradle |
376
375
 
377
376
  Multi-module builds are measured module by module: each compiles its own source
378
377
  set and forks its own JVM, so each gets a runtime and records evidence of its
@@ -391,10 +390,11 @@ report name the same thing.
391
390
  JUnit 4 on its own is not a platform engine and does not run on one. Maven and
392
391
  Gradle choose a test provider from what is on the classpath, so putting the
393
392
  platform there makes the build pick a provider that finds no engine and fail.
394
- For a Maven module, Supercov adds `junit-vintage-engine` to the copy — the
395
- platform's own way of running exactly those JUnit 4 tests through the lifecycle
396
- it listens to — and measures them; your own build still runs JUnit 4 as it did.
397
- A Gradle module is left alone and told about rather than broken.
393
+ Supercov adds `junit-vintage-engine` to the copy — the platform's own way of
394
+ running exactly those JUnit 4 tests through the lifecycle it listens to — and
395
+ measures them; your own build still runs JUnit 4 as it did. In a Gradle build
396
+ the copy also switches each JUnit 4 module's test tasks to `useJUnitPlatform()`,
397
+ leaving any task the build already put on the platform or on TestNG as it was.
398
398
 
399
399
  Supercov instruments an isolated copy and leaves your build file alone. In the
400
400
  copy it adds a test-scoped `junit-platform-launcher`, because the listener is
@@ -423,15 +423,11 @@ same goes for a loop over a constant, `while (true)`, which can only go one
423
423
  way. A Kotlin `contract { }` has to stay the first statement of its function,
424
424
  so the probe that records the function being entered is written after it.
425
425
 
426
- Every one of these is named in the run: ask for `supercov runs latest
426
+ Every one of these is named in the run: ask for `npx supercov runs latest
427
427
  limitations` and each appears with its file, its line, and why it was left
428
428
  alone. A source file the parser cannot read is declared there too, so a hole in
429
429
  the denominator stays visible after the build log is gone.
430
430
 
431
- For assertion maps, forms spelled `assertSomething`, `assertThat` or `fail` are
432
- inventoried, which covers JUnit, TestNG, AssertJ, Hamcrest and kotlin.test.
433
- Kotest's infix matchers are not.
434
-
435
431
  ```sh
436
432
  npx supercov -- mvn test
437
433
  npx supercov -- ./gradlew test
@@ -93,11 +93,18 @@ outside that loader. Supercov declares this boundary on the affected file,
93
93
  including on Python 3.9–3.11. It does not silently rewrite the child command,
94
94
  which could change imports, `sys.argv`, or process behavior.
95
95
 
96
- A file can therefore have tests that touched it through an import, while a
97
- separate direct execution makes its overall coverage unmeasured. Those lines
98
- are excluded from both the measured denominator and the uncovered-lines list.
99
- Inspect the file's measurement limitations before treating a zero denominator
100
- as complete coverage.
96
+ A file that tests also import stays measured: what the imports ran is
97
+ counted, and the file carries the limitation, because lines it shows as
98
+ uncovered may have run in the direct execution. Its numbers are a lower bound,
99
+ and the run reports its measurement as incomplete. A file that is only ever
100
+ run directly has nothing observed, and is left out of the measured denominator
101
+ and the uncovered-lines list; inspect its measurement limitations before
102
+ treating a zero denominator as complete coverage.
103
+
104
+ To have a script measured, run it through the import system:
105
+ `python -m package.module`, or, for a file whose name is not a module name
106
+ such as `bin/anti-drift-gate.py`, `runpy.run_path("bin/anti-drift-gate.py",
107
+ run_name="__main__")` from a test.
101
108
 
102
109
  ## Python subprocess output or timeout assertions fail
103
110