supercov 0.0.52 → 0.0.53

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 CHANGED
@@ -136,17 +136,23 @@ The denominator comes from source structure before the run, so adding or removin
136
136
 
137
137
  ## Install for your language
138
138
 
139
- The same binary ships through each language's own package manager, at the same version, from one release:
139
+ Measuring a project should not start by installing another language. The same binary, at the same version, from one release:
140
140
 
141
141
  ```bash
142
- npx supercov -- npm test # npm
143
- uvx --from supercov-cli supercov -- pytest # PyPI
144
- gem install supercov && supercov -- bundle exec rspec # RubyGems
145
- cargo binstall supercov && supercov -- cargo test # crates.io
142
+ npx supercov -- npm test # npm
143
+ uvx --from supercov-cli supercov -- pytest # PyPI
144
+ gem install supercov && supercov -- bundle exec rspec # RubyGems
145
+ cargo binstall supercov && supercov -- cargo test # crates.io
146
+ go run github.com/supercorp-ai/supercov/cmd/supercov@latest -- go test ./... # Go
147
+ brew install supercorp-ai/tap/supercov && supercov -- ./gradlew test # Homebrew
146
148
  ```
147
149
 
148
150
  `pip install supercov-cli` and `gem install supercov` install a wheel or gem that carries the binary for your platform; nothing is compiled. `cargo binstall` downloads that same binary from the GitHub release, while plain `cargo install supercov` builds it from source and needs Rust 1.95.
149
151
 
152
+ A Go project needs Go and nothing else. Like any `go run` with a version suffix it resolves by module path and ignores the `go.mod` in your current directory, so it neither needs nor touches your module.
153
+
154
+ Java and Kotlin have no registry of their own here, so a JVM project takes the binary directly -- Homebrew above, `npx` if Node is already present, or the platform archive from the [latest release](https://github.com/supercorp-ai/supercov/releases/latest). Maven and Gradle are driven as your test command, not as a plugin. Supercov adds the JUnit Platform launcher its measurement needs to the build file inside its own isolated workspace copy -- your `pom.xml` or `build.gradle` is never edited.
155
+
150
156
  ## Supported languages
151
157
 
152
158
  | Language | Status | Start with |
@@ -156,7 +162,7 @@ cargo binstall supercov && supercov -- cargo test # crates.io
156
162
  | Rust | Available | `npx supercov -- cargo test` |
157
163
  | Python | Available | `npx supercov -- pytest` |
158
164
  | Ruby | Available | `npx supercov -- rspec` |
159
- | Go | Available | `npx supercov -- go test ./...` |
165
+ | Go | Available | `go run github.com/supercorp-ai/supercov/cmd/supercov@latest -- go test ./...` |
160
166
  | Java | Available | `npx supercov -- mvn test` |
161
167
  | Kotlin | Available | `npx supercov -- ./gradlew test` |
162
168
  | Zig | Coming soon | — |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supercov",
3
- "version": "0.0.52",
3
+ "version": "0.0.53",
4
4
  "description": "Coverage for coding agents and software factories \ud83c\udf19",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -97,14 +97,14 @@
97
97
  "test:launcher": "go vet ./cmd/... && go test ./cmd/..."
98
98
  },
99
99
  "optionalDependencies": {
100
- "@supercov/cli-darwin-arm64": "0.0.52",
101
- "@supercov/cli-darwin-x64": "0.0.52",
102
- "@supercov/cli-linux-arm64-gnu": "0.0.52",
103
- "@supercov/cli-linux-arm64-musl": "0.0.52",
104
- "@supercov/cli-linux-x64-gnu": "0.0.52",
105
- "@supercov/cli-linux-x64-musl": "0.0.52",
106
- "@supercov/cli-win32-arm64": "0.0.52",
107
- "@supercov/cli-win32-x64": "0.0.52"
100
+ "@supercov/cli-darwin-arm64": "0.0.53",
101
+ "@supercov/cli-darwin-x64": "0.0.53",
102
+ "@supercov/cli-linux-arm64-gnu": "0.0.53",
103
+ "@supercov/cli-linux-arm64-musl": "0.0.53",
104
+ "@supercov/cli-linux-x64-gnu": "0.0.53",
105
+ "@supercov/cli-linux-x64-musl": "0.0.53",
106
+ "@supercov/cli-win32-arm64": "0.0.53",
107
+ "@supercov/cli-win32-x64": "0.0.53"
108
108
  },
109
109
  "peerDependencies": {
110
110
  "@playwright/test": ">=1.55.0",
@@ -751,8 +751,19 @@ function withNodeAssertionPhase(operation, source, callback) {
751
751
  const scope = context.scope;
752
752
  if (!scope)
753
753
  return callback();
754
- const existing = context.phaseId && assertionPhaseState(scope).phaseIds.has(context.phaseId);
755
- if (existing)
754
+ // A lexically instrumented occurrence is an authored assertion site in its
755
+ // own right, even when it runs inside another assertion's callback: the
756
+ // validator passed to assert.throws is the ordinary case, and its inner
757
+ // assertions are what claim *what* the error says. Skipping every nested
758
+ // phase left those with no passing occurrence, so they could never earn
759
+ // credit however carefully they were mapped.
760
+ //
761
+ // What must still be skipped is the same call seen twice -- the module proxy
762
+ // firing inside the lexical wrapper. That path has no lexical source and
763
+ // arrives as a lazy stack fallback, so the shape of `source` tells them
764
+ // apart without comparing coordinates the transform has moved.
765
+ const nested = context.phaseId && assertionPhaseState(scope).phaseIds.has(context.phaseId);
766
+ if (nested && typeof source !== "string")
756
767
  return callback();
757
768
  // A lexical occurrence already identifies its source. Stack fallback is lazy
758
769
  // and qualified so transformed coordinates cannot impersonate original ones.
@@ -1244,20 +1255,35 @@ function tryBegin(successId, catchId) {
1244
1255
  return { successId, catchId, caught: false };
1245
1256
  }
1246
1257
  function tryCatch(frame, value) {
1247
- frame.caught = true;
1258
+ // Record the outcome the moment it is known, not when the construct is left.
1259
+ // The commit used to sit in the generated `finally`, and `process.exit()` in
1260
+ // a catch body skips `finally` -- so a catch that ran and whose statements
1261
+ // were credited was still reported as never entered. Statement probes fire
1262
+ // in place and survived, which is what made the report contradict itself.
1263
+ if (!frame.caught) {
1264
+ frame.caught = true;
1265
+ coverageHit(frame.catchId);
1266
+ }
1248
1267
  return value;
1249
1268
  }
1250
1269
  function tryEnd(frame) {
1251
- coverageHit(frame.caught ? frame.catchId : frame.successId);
1270
+ // Completing without catching is only knowable here.
1271
+ if (!frame.caught) coverageHit(frame.successId);
1252
1272
  }
1253
1273
  function loopBegin(zeroId, enteredId) {
1254
1274
  return { zeroId, enteredId, entered: false };
1255
1275
  }
1256
1276
  function loopEntered(frame) {
1257
- frame.entered = true;
1277
+ // Same reason as tryCatch: a loop body that exits the process would
1278
+ // otherwise report zero iterations. Guarded so a loop still counts once,
1279
+ // however many times it iterates.
1280
+ if (!frame.entered) {
1281
+ frame.entered = true;
1282
+ coverageHit(frame.enteredId);
1283
+ }
1258
1284
  }
1259
1285
  function loopEnd(frame) {
1260
- coverageHit(frame.entered ? frame.enteredId : frame.zeroId);
1286
+ if (!frame.entered) coverageHit(frame.zeroId);
1261
1287
  }
1262
1288
  function mcdcBegin(id, meta) {
1263
1289
  if (!state.decisions.has(id)) {