supercov 4.1.1 → 4.2.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.
- package/docs/agent-loop.md +5 -0
- package/docs/cli.md +7 -0
- package/package.json +9 -9
- package/runtime/javascript/register.mjs +56 -12
- package/runtime/javascript/runtime.mjs +10 -2
package/docs/agent-loop.md
CHANGED
|
@@ -49,6 +49,11 @@ and file paths in place of the examples. For example,
|
|
|
49
49
|
Rust projects can use `cargo test`, Python projects `pytest`, and Ruby projects
|
|
50
50
|
`bundle exec rspec`. Keep the baseline and verification commands identical.
|
|
51
51
|
|
|
52
|
+
A loss in the comparison is not always a regression. An end-to-end suite
|
|
53
|
+
reaches slightly different code from one run to the next, and adding a test
|
|
54
|
+
changes no source: when `diff` notes that both runs measured the same source,
|
|
55
|
+
rerun before treating a loss as real.
|
|
56
|
+
|
|
52
57
|
The `line` query is useful before writing a test because it shows which tests
|
|
53
58
|
already reach that line. Extending a nearby test is often better than adding a
|
|
54
59
|
duplicate.
|
package/docs/cli.md
CHANGED
|
@@ -356,6 +356,13 @@ npx supercov diff <older-run> <newer-run>
|
|
|
356
356
|
expected behavior became covered without an unexplained regression elsewhere.
|
|
357
357
|
Neither input run is changed.
|
|
358
358
|
|
|
359
|
+
Two runs of the same code can differ a little: what an end-to-end suite
|
|
360
|
+
reaches depends on timing, on the order of tests, and on state outside the
|
|
361
|
+
code. When both runs measured the same source, `diff` says so under the
|
|
362
|
+
totals, and a loss it lists is not a change in the code. Run again before
|
|
363
|
+
acting on one. In `--json`, `inputs.sourceChanged` and `inputs.testsChanged`
|
|
364
|
+
say what differed between the two runs.
|
|
365
|
+
|
|
359
366
|
The same filters can focus a comparison:
|
|
360
367
|
|
|
361
368
|
```sh supercov
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supercov",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Coverage, security and code quality for coding agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"coverage",
|
|
@@ -119,14 +119,14 @@
|
|
|
119
119
|
"test:windows-tls": "node scripts/windows-tls-test.mjs"
|
|
120
120
|
},
|
|
121
121
|
"optionalDependencies": {
|
|
122
|
-
"@supercov/cli-darwin-arm64": "4.
|
|
123
|
-
"@supercov/cli-darwin-x64": "4.
|
|
124
|
-
"@supercov/cli-linux-arm64-gnu": "4.
|
|
125
|
-
"@supercov/cli-linux-arm64-musl": "4.
|
|
126
|
-
"@supercov/cli-linux-x64-gnu": "4.
|
|
127
|
-
"@supercov/cli-linux-x64-musl": "4.
|
|
128
|
-
"@supercov/cli-win32-arm64": "4.
|
|
129
|
-
"@supercov/cli-win32-x64": "4.
|
|
122
|
+
"@supercov/cli-darwin-arm64": "4.2.0",
|
|
123
|
+
"@supercov/cli-darwin-x64": "4.2.0",
|
|
124
|
+
"@supercov/cli-linux-arm64-gnu": "4.2.0",
|
|
125
|
+
"@supercov/cli-linux-arm64-musl": "4.2.0",
|
|
126
|
+
"@supercov/cli-linux-x64-gnu": "4.2.0",
|
|
127
|
+
"@supercov/cli-linux-x64-musl": "4.2.0",
|
|
128
|
+
"@supercov/cli-win32-arm64": "4.2.0",
|
|
129
|
+
"@supercov/cli-win32-x64": "4.2.0"
|
|
130
130
|
},
|
|
131
131
|
"peerDependencies": {
|
|
132
132
|
"@playwright/test": ">=1.55.0",
|
|
@@ -452,6 +452,16 @@ process.__SUPERCOV_FAILURE_OUTPUT__ ??= function supercovFailureOutput(runner) {
|
|
|
452
452
|
return failureOutput.names ? namedForItself(file, contents) : contents;
|
|
453
453
|
}
|
|
454
454
|
fs.readFileSync = supercovReadFileSync;
|
|
455
|
+
// graceful-fs, which Jest reads through, copies `fs` when it loads, and a
|
|
456
|
+
// tool that runs Jest in its own process has usually loaded it by then.
|
|
457
|
+
for (const [file, cached] of Object.entries(Module._cache)) {
|
|
458
|
+
if (/[\\/]graceful-fs[\\/]graceful-fs\.js$/.test(file) && typeof cached?.exports?.readFileSync === "function") {
|
|
459
|
+
try {
|
|
460
|
+
cached.exports.readFileSync = supercovReadFileSync;
|
|
461
|
+
}
|
|
462
|
+
catch { }
|
|
463
|
+
}
|
|
464
|
+
}
|
|
455
465
|
fs.promises.readFile = async function supercovReadFile(file, ...rest) {
|
|
456
466
|
const contents = await Reflect.apply(readPromise, this, [file, ...rest]);
|
|
457
467
|
return failureOutput.names && rewrittenUnder(copies, file) !== undefined && (typeof contents === "string" || Buffer.isBuffer(contents))
|
|
@@ -469,35 +479,69 @@ process.__SUPERCOV_FAILURE_OUTPUT__ ??= function supercovFailureOutput(runner) {
|
|
|
469
479
|
path.relative = supercovRelative;
|
|
470
480
|
syncBuiltinESMExports();
|
|
471
481
|
};
|
|
472
|
-
// Jest itself prints from here on.
|
|
473
|
-
//
|
|
474
|
-
//
|
|
482
|
+
// Jest itself prints from here on. Any other process is Jest's from the
|
|
483
|
+
// moment it loads Jest: a worker of its, or a tool that runs Jest in its own
|
|
484
|
+
// process (`require("jest").run()`, as react-scripts and Vue's CLI do), where
|
|
485
|
+
// a failure in a single test file still printed the line Supercov had
|
|
486
|
+
// wrapped. A worker of another tool's (a bundler minifies in the same kind
|
|
487
|
+
// of process) never loads it.
|
|
475
488
|
if (isJestEntrypoint)
|
|
476
489
|
process.__SUPERCOV_FAILURE_OUTPUT__("jest");
|
|
477
|
-
else
|
|
478
|
-
|
|
490
|
+
else {
|
|
491
|
+
const jest = /(?:^|[\\/])(?:jest|jest-cli|@jest[\\/]core|jest-runner)(?:[\\/]|$)/;
|
|
492
|
+
const load = Module._load;
|
|
493
|
+
let loaded = false;
|
|
494
|
+
Module._load = function supercovJestLoad(request, ...rest) {
|
|
495
|
+
if (!loaded && typeof request === "string" && request.includes("jest") && jest.test(request)) {
|
|
496
|
+
loaded = true;
|
|
497
|
+
process.__SUPERCOV_FAILURE_OUTPUT__("jest");
|
|
498
|
+
}
|
|
499
|
+
return Reflect.apply(load, this, [request, ...rest]);
|
|
500
|
+
};
|
|
501
|
+
}
|
|
479
502
|
// Next.js looks for lockfiles from its directory upwards, takes the outermost
|
|
480
503
|
// as its workspace root, and warns when it finds more than one. The copy
|
|
481
504
|
// holds the project's lockfile and lies inside the project, so every build
|
|
482
505
|
// under Supercov warned twice that the root "may not be correct", naming the
|
|
483
506
|
// copy's lockfile. The root Next picks is the project, which is right: it
|
|
484
|
-
// holds the copy and the dependencies. The copy's lockfile is
|
|
485
|
-
//
|
|
486
|
-
|
|
507
|
+
// holds the copy and the dependencies. The copy's lockfile is taken out of
|
|
508
|
+
// the warning, which is not printed when no other is left; one the project
|
|
509
|
+
// really has twice still warns.
|
|
510
|
+
//
|
|
511
|
+
// The warning is recognised by its text where Next prints it, in whatever
|
|
512
|
+
// process loads Next: started by its own command, by a server of the
|
|
513
|
+
// project's, or from a script. Next 15.4 to 16.4 print it through `warnOnce`.
|
|
514
|
+
{
|
|
487
515
|
const copy = fileURLToPath(new URL("../../", import.meta.url));
|
|
488
516
|
const copies = [...new Set([copy, (() => {
|
|
489
517
|
try {
|
|
490
|
-
return realpathSync(copy)
|
|
518
|
+
return `${realpathSync(copy)}${sep}`;
|
|
491
519
|
}
|
|
492
520
|
catch {
|
|
493
521
|
return copy;
|
|
494
522
|
}
|
|
495
523
|
})()])];
|
|
496
|
-
const
|
|
524
|
+
const quieted = `
|
|
525
|
+
;try { typeof warnOnce === "function" && (warnOnce = ((print) => function (...message) {
|
|
526
|
+
const text = message[0];
|
|
527
|
+
if (typeof text !== "string" || !text.includes("multiple lockfiles"))
|
|
528
|
+
return print.apply(this, message);
|
|
529
|
+
const copies = ${JSON.stringify(copies)};
|
|
530
|
+
const lines = text.split("\\n");
|
|
531
|
+
const listed = (line) => /^\\s+\\* \\S/.test(line);
|
|
532
|
+
const kept = lines.filter((line) => !listed(line) || !copies.some((copy) => line.trim().slice(2).startsWith(copy)));
|
|
533
|
+
if (!kept.some(listed))
|
|
534
|
+
return undefined;
|
|
535
|
+
return print.apply(this, [kept.join("\\n"), ...message.slice(1)]);
|
|
536
|
+
})(warnOnce)); } catch {}
|
|
537
|
+
`.replace(/\n\s*/g, " ");
|
|
497
538
|
const compile = Module.prototype._compile;
|
|
498
539
|
Module.prototype._compile = function _compile(content, filename, ...rest) {
|
|
499
|
-
if (typeof content === "string" &&
|
|
500
|
-
|
|
540
|
+
if (typeof content === "string" && typeof filename === "string" && filename.endsWith("log.js") &&
|
|
541
|
+
/[\\/]next[\\/]dist[\\/]build[\\/]output[\\/]log\.js$/.test(filename)) {
|
|
542
|
+
// After the last statement, before the source map's line.
|
|
543
|
+
const map = content.lastIndexOf("\n//# sourceMappingURL=");
|
|
544
|
+
content = map < 0 ? `${content}${quieted}` : `${content.slice(0, map)}${quieted}${content.slice(map)}`;
|
|
501
545
|
}
|
|
502
546
|
return Reflect.apply(compile, this, [content, filename, ...rest]);
|
|
503
547
|
};
|
|
@@ -926,8 +926,16 @@ function withNodeAssertionPhase(operation, source, callback) {
|
|
|
926
926
|
const lexical = typeof source === "string";
|
|
927
927
|
const context = currentRequestContext();
|
|
928
928
|
const scope = context.scope;
|
|
929
|
-
|
|
930
|
-
|
|
929
|
+
// No test is known to be running: a runner Supercov has no adapter in, such
|
|
930
|
+
// as Jest started from another tool's process. The assertion still fails
|
|
931
|
+
// with the frames its author would see.
|
|
932
|
+
if (!scope) {
|
|
933
|
+
try {
|
|
934
|
+
return callback();
|
|
935
|
+
} catch (error) {
|
|
936
|
+
throw cleanInstrumentationStack(error, lexical);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
931
939
|
// A lexically instrumented occurrence is an authored assertion site in its
|
|
932
940
|
// own right, even when it runs inside another assertion's callback: the
|
|
933
941
|
// validator passed to assert.throws is the ordinary case, and its inner
|