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.
@@ -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.1.1",
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.1.1",
123
- "@supercov/cli-darwin-x64": "4.1.1",
124
- "@supercov/cli-linux-arm64-gnu": "4.1.1",
125
- "@supercov/cli-linux-arm64-musl": "4.1.1",
126
- "@supercov/cli-linux-x64-gnu": "4.1.1",
127
- "@supercov/cli-linux-x64-musl": "4.1.1",
128
- "@supercov/cli-win32-arm64": "4.1.1",
129
- "@supercov/cli-win32-x64": "4.1.1"
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. A worker may be Jest's or another tool's
473
- // (a bundler minifies in the same kind of process): Jest's setup file names
474
- // the runner when a test file loads there.
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 if (process.env.JEST_WORKER_ID)
478
- process.__SUPERCOV_FAILURE_OUTPUT__();
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 left out of
485
- // what the warning counts, and one the project really has twice still warns.
486
- if (/\/node_modules\/(?:\.bin\/next$|next\/dist\/)/.test(entrypoint)) {
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 declared = "function warnDuplicatedLockFiles(lockFiles) {";
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" && /[\\/]next[\\/]dist[\\/]lib[\\/]find-root\.js$/.test(filename)) {
500
- content = content.replace(declared, `${declared} lockFiles = lockFiles.filter((file, index) => index === lockFiles.length - 1 || !${JSON.stringify(copies)}.some((copy) => file.startsWith(copy)));`);
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
- if (!scope)
930
- return callback();
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