supercov 4.23.0 → 4.24.1
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/docs/cli.md +10 -7
- package/docs/supported-suites.md +34 -0
- package/package.json +10 -10
- package/runtime/javascript/playwright.mjs +2 -2
- package/runtime/javascript/runtime.mjs +37 -19
package/docs/cli.md
CHANGED
|
@@ -742,15 +742,18 @@ Merge creates a new run. The inputs must describe the same source,
|
|
|
742
742
|
configuration, toolchain, schema, and coverage denominator. Supercov rejects an
|
|
743
743
|
incompatible merge rather than publishing a misleading aggregate.
|
|
744
744
|
|
|
745
|
-
With no run named, `merge` takes the runs recorded on the source as it is now
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
745
|
+
With no run named, `merge` takes the runs recorded on the source as it is now,
|
|
746
|
+
leaving out runs that are themselves a merge. It prints the runs it took. This
|
|
747
|
+
is what a script uses after measuring its suites, one after another or side by
|
|
748
|
+
side, without knowing what each run was called. Asked again with nothing new,
|
|
749
|
+
it names the merge already made.
|
|
750
750
|
|
|
751
751
|
A test that is in two of the runs is one test in the merge. If it failed in
|
|
752
|
-
either, it is failed, which is why a command
|
|
753
|
-
|
|
752
|
+
either, it is failed, which is why a command measured again in the same
|
|
753
|
+
project replaces its earlier runs there and is not merged with them. Runs of
|
|
754
|
+
one command measured on different machines and copied into one
|
|
755
|
+
`.supercov/runs` replace none of each other: each is a share of the suite, and
|
|
756
|
+
all are merged.
|
|
754
757
|
|
|
755
758
|
The runs may differ in their tests, the helpers beside them, and the test
|
|
756
759
|
runners' settings, as long as nothing that changed reaches what an earlier
|
package/docs/supported-suites.md
CHANGED
|
@@ -710,6 +710,40 @@ copy before its container starts, so an image left over from an earlier run
|
|
|
710
710
|
is never used. An image built from an earlier copy, by a command outside the
|
|
711
711
|
run, is left as it is, and the run says to build it in the test command.
|
|
712
712
|
|
|
713
|
+
A container the project is mounted into runs the copy's code whatever its
|
|
714
|
+
image, as `.:/workspace` in a compose file does: Supercov runs the command
|
|
715
|
+
in the copy, so the mount is of the copy. It gets the run's settings with
|
|
716
|
+
each path into the copy moved to where the container sees it, and the
|
|
717
|
+
project's `node_modules`, which the copy's packages link to, mounted
|
|
718
|
+
read-only at its own path. The copy's `.supercov`, where the run's evidence
|
|
719
|
+
is written, is mounted writable where the container sees it, so a copy
|
|
720
|
+
mounted read-only (`.:/workspace:ro`) is measured as well. A mount of one
|
|
721
|
+
directory of the project (`./src:/app/src`) is not the copy, and is left
|
|
722
|
+
alone.
|
|
723
|
+
|
|
724
|
+
The tests can run in such a container too. A command `docker exec` (or
|
|
725
|
+
`docker compose exec`) starts in it, `npx playwright test` say, gets the
|
|
726
|
+
test command's settings, moved the same way, with `NODE_OPTIONS` added to
|
|
727
|
+
the container's own. Playwright then runs under Supercov's preload and
|
|
728
|
+
generated configuration, as it does beside Supercov, and each test is
|
|
729
|
+
credited with what it ran:
|
|
730
|
+
|
|
731
|
+
```yaml
|
|
732
|
+
services:
|
|
733
|
+
tests:
|
|
734
|
+
image: mcr.microsoft.com/playwright:v1.56.0-noble
|
|
735
|
+
volumes: [".:/workspace"]
|
|
736
|
+
working_dir: /workspace
|
|
737
|
+
command: sleep infinity
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
```sh
|
|
741
|
+
npx supercov -- sh -c "docker compose up -d && docker compose exec tests npx playwright test"
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
When containers were given the run's settings and no test or coverage came
|
|
745
|
+
back from them, the run says so: what ran there is missing, not zero.
|
|
746
|
+
|
|
713
747
|
- Images pulled from a registry (`image:` with no `build:`, `postgres:16`)
|
|
714
748
|
run code nobody instrumented, and are left as they are.
|
|
715
749
|
- testcontainers' reaper, Ryuk, is pointed at the daemon's own socket
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supercov",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.24.1",
|
|
4
4
|
"description": "Coverage, security and code quality for coding agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"coverage",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"eval:skill": "claude plugin eval plugins/supercov --scaffold --trust-plugin --no-publish --model claude-sonnet-5 --judge-model claude-haiku-4-5 --threshold 0.66 --tag behavior trigger --allow-tools Bash Edit Write --max-cost-usd 15",
|
|
83
83
|
"eval:skill-triggers": "claude plugin eval plugins/supercov --scaffold --trust-plugin --no-publish --model claude-sonnet-5 --judge-model claude-haiku-4-5 --threshold 0.66 --tag trigger --ablation none --max-cost-usd 5",
|
|
84
84
|
"test:engine": "cargo fmt --all -- --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace && npm run test:runtime && npm run test:rust-assets && npm run test:engine-integrations",
|
|
85
|
-
"test:engine-integrations": "cargo build -p supercov && node scripts/html-report-integration.mjs && node scripts/rust-process-supervision.mjs && node scripts/rust-direct-node-integration.mjs && node scripts/rust-public-run-integration.mjs && node scripts/rust-embedded-runtime-integration.mjs && node scripts/rust-direct-vitest-integration.mjs && node scripts/rust-direct-jest-integration.mjs && node scripts/mocha-failure-output.mjs && node scripts/failure-output.mjs node ava tap && node scripts/side-by-side.mjs node && node scripts/merge-across.mjs && node scripts/import-line-evidence.mjs && node scripts/mock-reach.mjs && node scripts/rust-direct-playwright-integration.mjs && node scripts/rust-custom-browser-playwright-integration.mjs && node scripts/cross-origin-requests.mjs && node scripts/leaving-page.mjs && node scripts/shared-page.mjs && node scripts/context-headers.mjs && node scripts/context-cookies.mjs && node scripts/hidden-request-emit.mjs && node scripts/js-syntax-zoo.mjs && node scripts/ruby-syntax-zoo.mjs && node scripts/rust-syntax-zoo.mjs && node scripts/python-syntax-zoo.mjs && node scripts/go-syntax-zoo.mjs && node scripts/java-syntax-zoo.mjs && node scripts/background-by-time.mjs && node scripts/python-server-playwright.mjs && node scripts/ruby-server-playwright.mjs && node scripts/go-server-playwright.mjs && node scripts/jvm-server-playwright.mjs && node scripts/docker-server-playwright.mjs && node scripts/docker-clients-playwright.mjs && node scripts/rust-classic-browser-integration.mjs && node scripts/exec-handover.mjs && node scripts/rust-region-container.mjs && node scripts/rust-top-packages-integration.mjs && node scripts/rust-generic-esbuild-integration.mjs && node scripts/rust-generic-tsc-integration.mjs && node scripts/rust-generic-build-matrix.mjs && node scripts/rust-vite-playwright-integration.mjs && node scripts/workspace-crash-integration.mjs",
|
|
85
|
+
"test:engine-integrations": "cargo build -p supercov && node scripts/html-report-integration.mjs && node scripts/rust-process-supervision.mjs && node scripts/rust-direct-node-integration.mjs && node scripts/rust-public-run-integration.mjs && node scripts/rust-embedded-runtime-integration.mjs && node scripts/rust-direct-vitest-integration.mjs && node scripts/rust-direct-jest-integration.mjs && node scripts/mocha-failure-output.mjs && node scripts/failure-output.mjs node ava tap && node scripts/side-by-side.mjs node && node scripts/merge-across.mjs && node scripts/import-line-evidence.mjs && node scripts/mock-reach.mjs && node scripts/rust-direct-playwright-integration.mjs && node scripts/rust-custom-browser-playwright-integration.mjs && node scripts/cross-origin-requests.mjs && node scripts/leaving-page.mjs && node scripts/shared-page.mjs && node scripts/context-headers.mjs && node scripts/context-cookies.mjs && node scripts/hidden-request-emit.mjs && node scripts/js-syntax-zoo.mjs && node scripts/ruby-syntax-zoo.mjs && node scripts/rust-syntax-zoo.mjs && node scripts/python-syntax-zoo.mjs && node scripts/go-syntax-zoo.mjs && node scripts/java-syntax-zoo.mjs && node scripts/background-by-time.mjs && node scripts/python-server-playwright.mjs && node scripts/ruby-server-playwright.mjs && node scripts/go-server-playwright.mjs && node scripts/jvm-server-playwright.mjs && node scripts/docker-server-playwright.mjs && node scripts/docker-clients-playwright.mjs && node scripts/docker-exec-playwright.mjs && node scripts/rust-classic-browser-integration.mjs && node scripts/exec-handover.mjs && node scripts/rust-region-container.mjs && node scripts/rust-top-packages-integration.mjs && node scripts/rust-generic-esbuild-integration.mjs && node scripts/rust-generic-tsc-integration.mjs && node scripts/rust-generic-build-matrix.mjs && node scripts/rust-vite-playwright-integration.mjs && node scripts/workspace-crash-integration.mjs",
|
|
86
86
|
"test:platform": "cargo test --workspace && cargo build -p supercov && node scripts/rust-process-supervision.mjs && node scripts/workspace-crash-integration.mjs",
|
|
87
87
|
"test:native-package": "cargo build --release -p supercov && node scripts/native-package-integration.mjs && node scripts/native-release-set-integration.mjs",
|
|
88
88
|
"test:pypi-wheel": "node scripts/pypi-wheel-integration.mjs",
|
|
@@ -122,14 +122,14 @@
|
|
|
122
122
|
"test:windows-tls": "node scripts/windows-tls-test.mjs"
|
|
123
123
|
},
|
|
124
124
|
"optionalDependencies": {
|
|
125
|
-
"@supercov/cli-darwin-arm64": "4.
|
|
126
|
-
"@supercov/cli-darwin-x64": "4.
|
|
127
|
-
"@supercov/cli-linux-arm64-gnu": "4.
|
|
128
|
-
"@supercov/cli-linux-arm64-musl": "4.
|
|
129
|
-
"@supercov/cli-linux-x64-gnu": "4.
|
|
130
|
-
"@supercov/cli-linux-x64-musl": "4.
|
|
131
|
-
"@supercov/cli-win32-arm64": "4.
|
|
132
|
-
"@supercov/cli-win32-x64": "4.
|
|
125
|
+
"@supercov/cli-darwin-arm64": "4.24.1",
|
|
126
|
+
"@supercov/cli-darwin-x64": "4.24.1",
|
|
127
|
+
"@supercov/cli-linux-arm64-gnu": "4.24.1",
|
|
128
|
+
"@supercov/cli-linux-arm64-musl": "4.24.1",
|
|
129
|
+
"@supercov/cli-linux-x64-gnu": "4.24.1",
|
|
130
|
+
"@supercov/cli-linux-x64-musl": "4.24.1",
|
|
131
|
+
"@supercov/cli-win32-arm64": "4.24.1",
|
|
132
|
+
"@supercov/cli-win32-x64": "4.24.1"
|
|
133
133
|
},
|
|
134
134
|
"peerDependencies": {
|
|
135
135
|
"@playwright/test": ">=1.55.0",
|
|
@@ -973,7 +973,7 @@ function mergeSnapshots(snapshots) {
|
|
|
973
973
|
for (const event of snapshot?.events ?? [])
|
|
974
974
|
events.set(JSON.stringify(event), event);
|
|
975
975
|
for (const decision of snapshot?.decisions ?? []) {
|
|
976
|
-
const entry = vectorsOf(decisions, decision.
|
|
976
|
+
const entry = vectorsOf(decisions, decision.id, { id: decision.id });
|
|
977
977
|
for (const vector of decision.vectors ?? [])
|
|
978
978
|
entry.seen.set(JSON.stringify(vector), vector);
|
|
979
979
|
}
|
|
@@ -984,7 +984,7 @@ function mergeSnapshots(snapshots) {
|
|
|
984
984
|
}
|
|
985
985
|
}
|
|
986
986
|
return {
|
|
987
|
-
decisions: [...decisions.values()].map(({ seen,
|
|
987
|
+
decisions: [...decisions.values()].map(({ seen, id }) => ({ id, vectors: [...seen.values()] })),
|
|
988
988
|
hits: [...hits],
|
|
989
989
|
events: [...events.values()],
|
|
990
990
|
logicals: [...logicals.values()].map(({ seen, id }) => ({ id, vectors: [...seen.values()] })),
|
|
@@ -185,8 +185,8 @@ function createState() {
|
|
|
185
185
|
try {
|
|
186
186
|
const stored = JSON.parse((_a8 = localStorage.getItem(storageKey)) != null ? _a8 : "{}");
|
|
187
187
|
for (const snapshot of (_b = stored.decisions) != null ? _b : []) {
|
|
188
|
-
state2.decisions.set(snapshot.
|
|
189
|
-
|
|
188
|
+
state2.decisions.set(snapshot.id, {
|
|
189
|
+
id: snapshot.id,
|
|
190
190
|
vectors: new Map(snapshot.vectors.map((vector) => [vectorKey(vector), vector]))
|
|
191
191
|
});
|
|
192
192
|
}
|
|
@@ -304,7 +304,7 @@ runtimeGlobal.__SUPERCOV_ACTIVATE_PROBE_CONTEXT__ = (browserTestId, browserPhase
|
|
|
304
304
|
};
|
|
305
305
|
function decisionSnapshot() {
|
|
306
306
|
return [...state.decisions.values()].map((decision) => ({
|
|
307
|
-
|
|
307
|
+
id: decision.id,
|
|
308
308
|
vectors: [...decision.vectors.values()]
|
|
309
309
|
}));
|
|
310
310
|
}
|
|
@@ -470,7 +470,7 @@ function attemptKey(scope) {
|
|
|
470
470
|
}
|
|
471
471
|
function serverRecordKey(record) {
|
|
472
472
|
var _a8;
|
|
473
|
-
const suffix = record.type === "decision" ? `${record.
|
|
473
|
+
const suffix = record.type === "decision" ? `${record.id}:${vectorKey(record.vector)}` : record.id;
|
|
474
474
|
// Each request that names no test keeps its own first hits: the report
|
|
475
475
|
// credits them to the test running when it came, and a later request's
|
|
476
476
|
// work belongs to whichever test was running then.
|
|
@@ -625,6 +625,7 @@ function flushBackgroundAppends(fs) {
|
|
|
625
625
|
// all.
|
|
626
626
|
function makeEvidenceDirectory(fs, directory) {
|
|
627
627
|
const first = fs.mkdirSync(directory, { recursive: true });
|
|
628
|
+
markContainerWriting(fs);
|
|
628
629
|
if (!first || typeof process === "undefined" || !process.env || process.env.SUPERCOV_CONTAINER !== "1")
|
|
629
630
|
return;
|
|
630
631
|
let current = first;
|
|
@@ -640,6 +641,20 @@ function makeEvidenceDirectory(fs, directory) {
|
|
|
640
641
|
}
|
|
641
642
|
}
|
|
642
643
|
}
|
|
644
|
+
// A container Supercov's relay measured says, once, that its evidence could
|
|
645
|
+
// be written: one that never does -- a server stopped at its first probe, a
|
|
646
|
+
// directory it may not write -- is named when the run ends.
|
|
647
|
+
function markContainerWriting(fs) {
|
|
648
|
+
const mark = typeof process !== "undefined" && process.env ? process.env.SUPERCOV_CONTAINER_MARK : void 0;
|
|
649
|
+
if (!mark || state.containerMarked)
|
|
650
|
+
return;
|
|
651
|
+
state.containerMarked = true;
|
|
652
|
+
try {
|
|
653
|
+
fs.mkdirSync(mark.slice(0, mark.lastIndexOf("/")), { recursive: true });
|
|
654
|
+
fs.writeFileSync(mark, "");
|
|
655
|
+
} catch (e) {
|
|
656
|
+
}
|
|
657
|
+
}
|
|
643
658
|
function writeBackgroundLines(fs, runId, payload) {
|
|
644
659
|
var _a8;
|
|
645
660
|
const directory = backgroundEvidenceDirectory(runId);
|
|
@@ -982,7 +997,7 @@ function appendServer(record) {
|
|
|
982
997
|
if (record.type === "decision") {
|
|
983
998
|
recordBrowserEvent(__spreadProps(__spreadValues({
|
|
984
999
|
type: "decision",
|
|
985
|
-
id: record.
|
|
1000
|
+
id: record.id,
|
|
986
1001
|
vector: record.vector,
|
|
987
1002
|
timestampMs
|
|
988
1003
|
}, record.phaseId ? { phaseId: record.phaseId } : {}), {
|
|
@@ -1801,11 +1816,14 @@ function recordCoverageHit(id) {
|
|
|
1801
1816
|
function registerProbeV2(definition) {
|
|
1802
1817
|
var _a8, _b;
|
|
1803
1818
|
const file = {
|
|
1819
|
+
// Each decision's id and its number of conditions: the module carries
|
|
1820
|
+
// no source text, which the manifest holds.
|
|
1804
1821
|
decisions: definition.decisions,
|
|
1822
|
+
decisionWidths: definition.decisionWidths,
|
|
1805
1823
|
pointIds: definition.pointIds,
|
|
1806
1824
|
clock: state.probeV2Clock,
|
|
1807
1825
|
hitEpochs: new Uint32Array(definition.pointIds.length),
|
|
1808
|
-
decisionEpochs: definition.
|
|
1826
|
+
decisionEpochs: definition.decisionWidths.map((width) => width <= 6 ? new Uint32Array(2 * 3 ** width) : /* @__PURE__ */ new Map()),
|
|
1809
1827
|
decisionVectorCounts: definition.decisions.map((_, index) => {
|
|
1810
1828
|
var _a9, _b2;
|
|
1811
1829
|
return (_b2 = (_a9 = definition.decisionVectorCounts) == null ? void 0 : _a9[index]) != null ? _b2 : 0;
|
|
@@ -1898,8 +1916,8 @@ function mcdcEndV2(file, decisionIndex, encoded, value) {
|
|
|
1898
1916
|
}
|
|
1899
1917
|
function recordMcdcEndV2(file, decisionIndex, encoded, value) {
|
|
1900
1918
|
var _a8, _b;
|
|
1901
|
-
const
|
|
1902
|
-
if (!
|
|
1919
|
+
const id = file.decisions[decisionIndex];
|
|
1920
|
+
if (!id || !Number.isSafeInteger(encoded) || encoded < 0)
|
|
1903
1921
|
return value;
|
|
1904
1922
|
const outcome = Boolean(value);
|
|
1905
1923
|
const vectorIndex = encoded * 2 + (outcome ? 1 : 0);
|
|
@@ -1934,10 +1952,10 @@ function recordMcdcEndV2(file, decisionIndex, encoded, value) {
|
|
|
1934
1952
|
if (observedCount >= expectedCount)
|
|
1935
1953
|
file.decisionCompleteEpochs[decisionIndex] = epoch;
|
|
1936
1954
|
}
|
|
1937
|
-
if (!state.decisions.has(
|
|
1938
|
-
state.decisions.set(
|
|
1939
|
-
const vector = decodeProbeV2Vector(
|
|
1940
|
-
return vector ? mcdcEnd({
|
|
1955
|
+
if (!state.decisions.has(id))
|
|
1956
|
+
state.decisions.set(id, { id, vectors: /* @__PURE__ */ new Map() });
|
|
1957
|
+
const vector = decodeProbeV2Vector(file.decisionWidths[decisionIndex], encoded, outcome);
|
|
1958
|
+
return vector ? mcdcEnd({ id, values: vector.values }, value) : value;
|
|
1941
1959
|
} finally {
|
|
1942
1960
|
if (fallbackEpoch)
|
|
1943
1961
|
file.clock.epoch = previousEpoch;
|
|
@@ -2224,13 +2242,13 @@ function loopEntered(frame) {
|
|
|
2224
2242
|
function loopEnd(frame) {
|
|
2225
2243
|
if (!frame.entered) coverageHit(frame.zeroId);
|
|
2226
2244
|
}
|
|
2227
|
-
function mcdcBegin(id,
|
|
2245
|
+
function mcdcBegin(id, width) {
|
|
2228
2246
|
if (!state.decisions.has(id)) {
|
|
2229
|
-
state.decisions.set(id, {
|
|
2247
|
+
state.decisions.set(id, { id, vectors: /* @__PURE__ */ new Map() });
|
|
2230
2248
|
}
|
|
2231
2249
|
return {
|
|
2232
|
-
|
|
2233
|
-
values: Array.from({ length:
|
|
2250
|
+
id,
|
|
2251
|
+
values: Array.from({ length: width }, () => null)
|
|
2234
2252
|
};
|
|
2235
2253
|
}
|
|
2236
2254
|
function mcdcCondition(frame, index, value) {
|
|
@@ -2238,7 +2256,7 @@ function mcdcCondition(frame, index, value) {
|
|
|
2238
2256
|
return value;
|
|
2239
2257
|
}
|
|
2240
2258
|
function mcdcEnd(frame, value) {
|
|
2241
|
-
const decision = state.decisions.get(frame.
|
|
2259
|
+
const decision = state.decisions.get(frame.id);
|
|
2242
2260
|
if (!decision)
|
|
2243
2261
|
return value;
|
|
2244
2262
|
const vector = { values: frame.values, outcome: Boolean(value) };
|
|
@@ -2249,7 +2267,7 @@ function mcdcEnd(frame, value) {
|
|
|
2249
2267
|
if (isBrowser) {
|
|
2250
2268
|
if (recordBrowserEvent(__spreadProps(__spreadValues({
|
|
2251
2269
|
type: "decision",
|
|
2252
|
-
id: decision.
|
|
2270
|
+
id: decision.id,
|
|
2253
2271
|
vector,
|
|
2254
2272
|
timestampMs
|
|
2255
2273
|
}, phaseId ? { phaseId } : {}), {
|
|
@@ -2259,7 +2277,7 @@ function mcdcEnd(frame, value) {
|
|
|
2259
2277
|
} else {
|
|
2260
2278
|
appendServer(__spreadValues({
|
|
2261
2279
|
type: "decision",
|
|
2262
|
-
|
|
2280
|
+
id: decision.id,
|
|
2263
2281
|
vector,
|
|
2264
2282
|
timestampMs
|
|
2265
2283
|
}, phaseId ? { phaseId } : {}));
|