@t09tanaka/test-gates 0.3.2 → 0.3.4

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/dist/index.cjs CHANGED
@@ -514,6 +514,24 @@ function resolveSettings(raw) {
514
514
 
515
515
  // src/core/selfcheck.ts
516
516
  var EXCLUDE_ENV = "TEST_GATES_EXCLUDE_SPEC";
517
+ var EXCLUDE_ALL_ENV = "TEST_GATES_EXCLUDE_SPECS";
518
+ function excludedSpecs(env) {
519
+ const one = env[EXCLUDE_ENV];
520
+ const several = env[EXCLUDE_ALL_ENV];
521
+ const excluded = one === void 0 || one === "" ? [] : [one];
522
+ if (several === void 0 || several === "") {
523
+ return excluded;
524
+ }
525
+ let listed = null;
526
+ try {
527
+ listed = JSON.parse(several);
528
+ } catch {
529
+ }
530
+ if (!Array.isArray(listed) || !listed.every((spec) => typeof spec === "string")) {
531
+ throw new Error(`test-gates: ${EXCLUDE_ALL_ENV} must be a JSON list of spec paths`);
532
+ }
533
+ return [...excluded, ...listed];
534
+ }
517
535
  var THRESHOLD_MESSAGE = /threshold|coverage data for/i;
518
536
  function escapeRegExp(text) {
519
537
  return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -561,19 +579,22 @@ function looksLikeSpec(gatePath, settings) {
561
579
  // src/fs.ts
562
580
  var import_node_fs = __toESM(require("fs"), 1);
563
581
  var import_node_path = __toESM(require("path"), 1);
564
- function existsExact(rootDir, relativePath, kind = "file") {
582
+ function existsExact(rootDir, relativePath, kind = "file", listing = /* @__PURE__ */ new Map()) {
565
583
  let current = rootDir;
566
584
  for (const segment of relativePath.split("/")) {
567
585
  if (segment === "" || segment === ".") {
568
586
  continue;
569
587
  }
570
- let entries;
571
- try {
572
- entries = import_node_fs.default.readdirSync(current);
573
- } catch {
574
- return false;
588
+ let entries = listing.get(current);
589
+ if (entries === void 0) {
590
+ try {
591
+ entries = import_node_fs.default.readdirSync(current);
592
+ } catch {
593
+ entries = null;
594
+ }
595
+ listing.set(current, entries);
575
596
  }
576
- if (!entries.includes(segment)) {
597
+ if (entries === null || !entries.includes(segment)) {
577
598
  return false;
578
599
  }
579
600
  current = import_node_path.default.join(current, segment);
@@ -611,9 +632,12 @@ function readManifest(dir) {
611
632
  }
612
633
  throw error;
613
634
  }
635
+ const listing = /* @__PURE__ */ new Map();
614
636
  const gates = parsed.gates.map((gate) => {
615
637
  const specCandidates = specCandidatesFor(gate.path, settings);
616
- const existingSpecs = specCandidates.filter((candidate) => existsExact(absoluteDir, candidate));
638
+ const existingSpecs = specCandidates.filter(
639
+ (candidate) => existsExact(absoluteDir, candidate, "file", listing)
640
+ );
617
641
  return {
618
642
  ...gate,
619
643
  specCandidates,
@@ -640,8 +664,8 @@ function loadGates(dir = process.cwd()) {
640
664
  return manifest;
641
665
  }
642
666
  function gateSpecs(manifest, env = process.env) {
643
- const excluded = env[EXCLUDE_ENV];
644
- return manifest.gates.map((gate) => gate.spec).filter((spec) => spec !== null && spec !== excluded);
667
+ const excluded = excludedSpecs(env);
668
+ return manifest.gates.map((gate) => gate.spec).filter((spec) => spec !== null && !excluded.includes(spec));
645
669
  }
646
670
 
647
671
  // src/commands/check.ts
@@ -957,6 +981,7 @@ function isPlainRelativePath(file) {
957
981
  function checkGates(manifest) {
958
982
  const { dir, settings } = manifest;
959
983
  const violations = [...manifest.violations];
984
+ const listing = /* @__PURE__ */ new Map();
960
985
  for (const gate of manifest.gates) {
961
986
  if (!isPlainRelativePath(gate.path)) {
962
987
  violations.push({
@@ -984,7 +1009,7 @@ function checkGates(manifest) {
984
1009
  });
985
1010
  }
986
1011
  }
987
- if (!existsExact(dir, gate.path)) {
1012
+ if (!existsExact(dir, gate.path, "file", listing)) {
988
1013
  violations.push({
989
1014
  file: gate.path,
990
1015
  message: "file not found (the check is case-sensitive)"
@@ -1013,7 +1038,7 @@ function checkGates(manifest) {
1013
1038
  violations.push(...scanSpecSource(gate.path, specPath, readText(dir, specPath), settings));
1014
1039
  }
1015
1040
  for (const candidate of manifest.candidates) {
1016
- if (!isPlainRelativePath(candidate.path) || !existsExact(dir, candidate.path, "any")) {
1041
+ if (!isPlainRelativePath(candidate.path) || !existsExact(dir, candidate.path, "any", listing)) {
1017
1042
  violations.push({
1018
1043
  file: MANIFEST_FILE,
1019
1044
  message: `${candidate.path}: candidate not found. Remove it, or fix the path`
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  readText,
21
21
  resolveSettings,
22
22
  validateEquivalentMutants
23
- } from "./chunk-F7WBEFIL.js";
23
+ } from "./chunk-XMZ6NBV7.js";
24
24
 
25
25
  // src/commands/check.ts
26
26
  import fs from "fs";
@@ -335,6 +335,7 @@ function isPlainRelativePath(file) {
335
335
  function checkGates(manifest) {
336
336
  const { dir, settings } = manifest;
337
337
  const violations = [...manifest.violations];
338
+ const listing = /* @__PURE__ */ new Map();
338
339
  for (const gate of manifest.gates) {
339
340
  if (!isPlainRelativePath(gate.path)) {
340
341
  violations.push({
@@ -362,7 +363,7 @@ function checkGates(manifest) {
362
363
  });
363
364
  }
364
365
  }
365
- if (!existsExact(dir, gate.path)) {
366
+ if (!existsExact(dir, gate.path, "file", listing)) {
366
367
  violations.push({
367
368
  file: gate.path,
368
369
  message: "file not found (the check is case-sensitive)"
@@ -391,7 +392,7 @@ function checkGates(manifest) {
391
392
  violations.push(...scanSpecSource(gate.path, specPath, readText(dir, specPath), settings));
392
393
  }
393
394
  for (const candidate of manifest.candidates) {
394
- if (!isPlainRelativePath(candidate.path) || !existsExact(dir, candidate.path, "any")) {
395
+ if (!isPlainRelativePath(candidate.path) || !existsExact(dir, candidate.path, "any", listing)) {
395
396
  violations.push({
396
397
  file: MANIFEST_FILE,
397
398
  message: `${candidate.path}: candidate not found. Remove it, or fix the path`
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/jest-touch-reporter.ts
31
+ var jest_touch_reporter_exports = {};
32
+ __export(jest_touch_reporter_exports, {
33
+ default: () => TouchReporter
34
+ });
35
+ module.exports = __toCommonJS(jest_touch_reporter_exports);
36
+ var import_node_module = require("module");
37
+ var import_node_path2 = __toESM(require("path"), 1);
38
+
39
+ // src/touch.ts
40
+ var import_node_crypto = require("crypto");
41
+ var import_node_fs = __toESM(require("fs"), 1);
42
+ var import_node_path = __toESM(require("path"), 1);
43
+ var import_node_url = require("url");
44
+ var TOUCH_ENV = "TEST_GATES_TOUCH_DIR";
45
+ function isObject(value) {
46
+ return typeof value === "object" && value !== null && !Array.isArray(value);
47
+ }
48
+ function countersOf(coverage) {
49
+ return {
50
+ s: Object.values(coverage.s ?? {}),
51
+ f: Object.values(coverage.f ?? {}),
52
+ b: Object.values(coverage.b ?? {}).flat()
53
+ };
54
+ }
55
+ function countersOfAll(coverage) {
56
+ if (!isObject(coverage)) {
57
+ return null;
58
+ }
59
+ const files = {};
60
+ for (const [file, counters] of Object.entries(coverage)) {
61
+ if (!isObject(counters)) {
62
+ return null;
63
+ }
64
+ files[file] = countersOf(counters);
65
+ }
66
+ return files;
67
+ }
68
+ function add(before, now) {
69
+ return before.length === now.length ? before.map((count, index) => count + (now[index] ?? 0)) : [-1];
70
+ }
71
+ function writeSpecNote(directory, spec, coverage) {
72
+ const files = countersOfAll(coverage) ?? {};
73
+ const specs = import_node_path.default.join(directory, "specs");
74
+ import_node_fs.default.mkdirSync(specs, { recursive: true });
75
+ const file = import_node_path.default.join(specs, `${(0, import_node_crypto.createHash)("sha256").update(spec).digest("hex")}.json`);
76
+ let before = {};
77
+ try {
78
+ before = JSON.parse(import_node_fs.default.readFileSync(file, "utf8")).files;
79
+ } catch {
80
+ }
81
+ for (const [name, counters] of Object.entries(before)) {
82
+ const now = files[name];
83
+ files[name] = now === void 0 ? counters : { s: add(counters.s, now.s), f: add(counters.f, now.f), b: add(counters.b, now.b) };
84
+ }
85
+ const note = { spec, files };
86
+ import_node_fs.default.writeFileSync(file, JSON.stringify(note));
87
+ }
88
+ function writeRunInfo(directory, run) {
89
+ import_node_fs.default.mkdirSync(directory, { recursive: true });
90
+ import_node_fs.default.writeFileSync(import_node_path.default.join(directory, "run.json"), JSON.stringify(run));
91
+ }
92
+
93
+ // src/jest-touch-reporter.ts
94
+ function jestVersion(rootDir) {
95
+ for (const name of ["jest/package.json", "@jest/core/package.json"]) {
96
+ try {
97
+ const from = (0, import_node_module.createRequire)(import_node_path2.default.join(typeof rootDir === "string" ? rootDir : ".", "x.js"));
98
+ const { version } = from(name);
99
+ if (typeof version === "string") {
100
+ return version;
101
+ }
102
+ } catch {
103
+ }
104
+ }
105
+ return "unknown";
106
+ }
107
+ var TouchReporter = class {
108
+ constructor(globalConfig = {}) {
109
+ const directory = process.env[TOUCH_ENV];
110
+ if (directory) {
111
+ writeRunInfo(directory, {
112
+ runner: "jest",
113
+ version: jestVersion(globalConfig.rootDir),
114
+ provider: typeof globalConfig.coverageProvider === "string" ? globalConfig.coverageProvider : "unknown",
115
+ pool: "default",
116
+ isolate: true,
117
+ // Without the `projects` option the list is empty, or names the one root.
118
+ projects: Array.isArray(globalConfig.projects) ? Math.max(1, globalConfig.projects.length) : 0,
119
+ problem: null
120
+ });
121
+ }
122
+ }
123
+ onTestResult(_test, result) {
124
+ const directory = process.env[TOUCH_ENV];
125
+ if (directory) {
126
+ writeSpecNote(directory, result.testFilePath, result.coverage);
127
+ }
128
+ }
129
+ };
@@ -0,0 +1,19 @@
1
+ interface GlobalConfig {
2
+ rootDir?: unknown;
3
+ coverageProvider?: unknown;
4
+ projects?: unknown;
5
+ }
6
+ /**
7
+ * Jest reporter that `createJestGatesConfig` adds while `test-gates selfcheck` asks for it:
8
+ * writes down what each spec file executed, from the coverage Jest itself collected for that
9
+ * file, and how the run ran.
10
+ */
11
+ declare class TouchReporter {
12
+ constructor(globalConfig?: GlobalConfig);
13
+ onTestResult(_test: unknown, result: {
14
+ testFilePath: string;
15
+ coverage?: unknown;
16
+ }): void;
17
+ }
18
+
19
+ export { TouchReporter as default };
@@ -0,0 +1,19 @@
1
+ interface GlobalConfig {
2
+ rootDir?: unknown;
3
+ coverageProvider?: unknown;
4
+ projects?: unknown;
5
+ }
6
+ /**
7
+ * Jest reporter that `createJestGatesConfig` adds while `test-gates selfcheck` asks for it:
8
+ * writes down what each spec file executed, from the coverage Jest itself collected for that
9
+ * file, and how the run ran.
10
+ */
11
+ declare class TouchReporter {
12
+ constructor(globalConfig?: GlobalConfig);
13
+ onTestResult(_test: unknown, result: {
14
+ testFilePath: string;
15
+ coverage?: unknown;
16
+ }): void;
17
+ }
18
+
19
+ export { TouchReporter as default };
@@ -0,0 +1,48 @@
1
+ import {
2
+ TOUCH_ENV,
3
+ writeRunInfo,
4
+ writeSpecNote
5
+ } from "./chunk-TOKHAUFX.js";
6
+
7
+ // src/jest-touch-reporter.ts
8
+ import { createRequire } from "module";
9
+ import path from "path";
10
+ function jestVersion(rootDir) {
11
+ for (const name of ["jest/package.json", "@jest/core/package.json"]) {
12
+ try {
13
+ const from = createRequire(path.join(typeof rootDir === "string" ? rootDir : ".", "x.js"));
14
+ const { version } = from(name);
15
+ if (typeof version === "string") {
16
+ return version;
17
+ }
18
+ } catch {
19
+ }
20
+ }
21
+ return "unknown";
22
+ }
23
+ var TouchReporter = class {
24
+ constructor(globalConfig = {}) {
25
+ const directory = process.env[TOUCH_ENV];
26
+ if (directory) {
27
+ writeRunInfo(directory, {
28
+ runner: "jest",
29
+ version: jestVersion(globalConfig.rootDir),
30
+ provider: typeof globalConfig.coverageProvider === "string" ? globalConfig.coverageProvider : "unknown",
31
+ pool: "default",
32
+ isolate: true,
33
+ // Without the `projects` option the list is empty, or names the one root.
34
+ projects: Array.isArray(globalConfig.projects) ? Math.max(1, globalConfig.projects.length) : 0,
35
+ problem: null
36
+ });
37
+ }
38
+ }
39
+ onTestResult(_test, result) {
40
+ const directory = process.env[TOUCH_ENV];
41
+ if (directory) {
42
+ writeSpecNote(directory, result.testFilePath, result.coverage);
43
+ }
44
+ }
45
+ };
46
+ export {
47
+ TouchReporter as default
48
+ };
package/dist/jest.cjs CHANGED
@@ -33,7 +33,13 @@ __export(jest_exports, {
33
33
  createJestGatesConfig: () => createJestGatesConfig
34
34
  });
35
35
  module.exports = __toCommonJS(jest_exports);
36
- var import_node_path4 = __toESM(require("path"), 1);
36
+
37
+ // node_modules/tsup/assets/cjs_shims.js
38
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
39
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
40
+
41
+ // src/jest.ts
42
+ var import_node_path5 = __toESM(require("path"), 1);
37
43
 
38
44
  // src/load.ts
39
45
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -490,6 +496,24 @@ function resolveSettings(raw) {
490
496
 
491
497
  // src/core/selfcheck.ts
492
498
  var EXCLUDE_ENV = "TEST_GATES_EXCLUDE_SPEC";
499
+ var EXCLUDE_ALL_ENV = "TEST_GATES_EXCLUDE_SPECS";
500
+ function excludedSpecs(env) {
501
+ const one = env[EXCLUDE_ENV];
502
+ const several = env[EXCLUDE_ALL_ENV];
503
+ const excluded = one === void 0 || one === "" ? [] : [one];
504
+ if (several === void 0 || several === "") {
505
+ return excluded;
506
+ }
507
+ let listed = null;
508
+ try {
509
+ listed = JSON.parse(several);
510
+ } catch {
511
+ }
512
+ if (!Array.isArray(listed) || !listed.every((spec) => typeof spec === "string")) {
513
+ throw new Error(`test-gates: ${EXCLUDE_ALL_ENV} must be a JSON list of spec paths`);
514
+ }
515
+ return [...excluded, ...listed];
516
+ }
493
517
 
494
518
  // src/core/spec-path.ts
495
519
  function gateExtensionOf(gatePath, gateExtensions) {
@@ -514,19 +538,22 @@ function specCandidatesFor(gatePath, settings) {
514
538
  // src/fs.ts
515
539
  var import_node_fs = __toESM(require("fs"), 1);
516
540
  var import_node_path = __toESM(require("path"), 1);
517
- function existsExact(rootDir, relativePath, kind = "file") {
541
+ function existsExact(rootDir, relativePath, kind = "file", listing = /* @__PURE__ */ new Map()) {
518
542
  let current = rootDir;
519
543
  for (const segment of relativePath.split("/")) {
520
544
  if (segment === "" || segment === ".") {
521
545
  continue;
522
546
  }
523
- let entries;
524
- try {
525
- entries = import_node_fs.default.readdirSync(current);
526
- } catch {
527
- return false;
547
+ let entries = listing.get(current);
548
+ if (entries === void 0) {
549
+ try {
550
+ entries = import_node_fs.default.readdirSync(current);
551
+ } catch {
552
+ entries = null;
553
+ }
554
+ listing.set(current, entries);
528
555
  }
529
- if (!entries.includes(segment)) {
556
+ if (entries === null || !entries.includes(segment)) {
530
557
  return false;
531
558
  }
532
559
  current = import_node_path.default.join(current, segment);
@@ -561,9 +588,12 @@ function readManifest(dir) {
561
588
  }
562
589
  throw error;
563
590
  }
591
+ const listing = /* @__PURE__ */ new Map();
564
592
  const gates = parsed.gates.map((gate) => {
565
593
  const specCandidates = specCandidatesFor(gate.path, settings);
566
- const existingSpecs = specCandidates.filter((candidate) => existsExact(absoluteDir, candidate));
594
+ const existingSpecs = specCandidates.filter(
595
+ (candidate) => existsExact(absoluteDir, candidate, "file", listing)
596
+ );
567
597
  return {
568
598
  ...gate,
569
599
  specCandidates,
@@ -590,8 +620,8 @@ function loadGates(dir = process.cwd()) {
590
620
  return manifest;
591
621
  }
592
622
  function gateSpecs(manifest, env = process.env) {
593
- const excluded = env[EXCLUDE_ENV];
594
- return manifest.gates.map((gate) => gate.spec).filter((spec) => spec !== null && spec !== excluded);
623
+ const excluded = excludedSpecs(env);
624
+ return manifest.gates.map((gate) => gate.spec).filter((spec) => spec !== null && !excluded.includes(spec));
595
625
  }
596
626
  var NO_SPEC_PATTERN = "__test_gates_no_spec_left__";
597
627
 
@@ -609,6 +639,21 @@ function resolveRootDir(rootDir) {
609
639
  return import_node_path3.default.resolve(rootDir);
610
640
  }
611
641
 
642
+ // src/touch.ts
643
+ var import_node_crypto = require("crypto");
644
+ var import_node_fs3 = __toESM(require("fs"), 1);
645
+ var import_node_path4 = __toESM(require("path"), 1);
646
+ var import_node_url2 = require("url");
647
+ var TOUCH_ENV = "TEST_GATES_TOUCH_DIR";
648
+ function jestReporterFile(moduleUrl) {
649
+ const directory = import_node_path4.default.dirname((0, import_node_url2.fileURLToPath)(moduleUrl));
650
+ const found = [".cjs", ".ts"].map((extension) => import_node_path4.default.join(directory, `jest-touch-reporter${extension}`)).find((file) => import_node_fs3.default.existsSync(file));
651
+ if (found === void 0) {
652
+ throw new Error(`test-gates: jest-touch-reporter is missing in ${directory}`);
653
+ }
654
+ return found;
655
+ }
656
+
612
657
  // src/jest.ts
613
658
  function createJestGatesConfig(options = {}) {
614
659
  const { rootDir: rootDirOption, ...overrides } = options;
@@ -620,6 +665,19 @@ function createJestGatesConfig(options = {}) {
620
665
  coverageReporters: ["text"],
621
666
  coverageDirectory: "<rootDir>/coverage/gates",
622
667
  ...overrides,
668
+ // While `test-gates selfcheck` asks for it: write down what each spec executed.
669
+ ...process.env[TOUCH_ENV] ? {
670
+ reporters: [
671
+ ...Array.isArray(overrides.reporters) ? overrides.reporters : ["default"],
672
+ jestReporterFile(importMetaUrl)
673
+ ],
674
+ // The runner's own total, to check the notes against.
675
+ coverageReporters: [
676
+ ...Array.isArray(overrides.coverageReporters) ? overrides.coverageReporters : ["text"],
677
+ "json"
678
+ ],
679
+ coverageDirectory: import_node_path5.default.join(process.env[TOUCH_ENV], "coverage")
680
+ } : {},
623
681
  rootDir,
624
682
  // Never an empty array: Jest would fall back to its default pattern and run every test.
625
683
  testMatch: (specs.length > 0 ? specs : [NO_SPEC_PATTERN]).map((spec) => `<rootDir>/${spec}`),
@@ -630,7 +688,7 @@ function createJestGatesConfig(options = {}) {
630
688
  coverageProvider: "babel",
631
689
  collectCoverageFrom: manifest.gates.map((gate) => `<rootDir>/${gate.path}`),
632
690
  coverageThreshold: Object.fromEntries(
633
- manifest.gates.map((gate) => [import_node_path4.default.join(rootDir, gate.path), { ...FULL_COVERAGE }])
691
+ manifest.gates.map((gate) => [import_node_path5.default.join(rootDir, gate.path), { ...FULL_COVERAGE }])
634
692
  )
635
693
  };
636
694
  }
package/dist/jest.js CHANGED
@@ -1,12 +1,16 @@
1
+ import {
2
+ TOUCH_ENV,
3
+ jestReporterFile
4
+ } from "./chunk-TOKHAUFX.js";
1
5
  import {
2
6
  FULL_COVERAGE,
3
7
  resolveRootDir
4
- } from "./chunk-4HAJIKDZ.js";
8
+ } from "./chunk-C4R3RDHF.js";
5
9
  import {
6
10
  NO_SPEC_PATTERN,
7
11
  gateSpecs,
8
12
  loadGates
9
- } from "./chunk-F7WBEFIL.js";
13
+ } from "./chunk-XMZ6NBV7.js";
10
14
 
11
15
  // src/jest.ts
12
16
  import path from "path";
@@ -20,6 +24,19 @@ function createJestGatesConfig(options = {}) {
20
24
  coverageReporters: ["text"],
21
25
  coverageDirectory: "<rootDir>/coverage/gates",
22
26
  ...overrides,
27
+ // While `test-gates selfcheck` asks for it: write down what each spec executed.
28
+ ...process.env[TOUCH_ENV] ? {
29
+ reporters: [
30
+ ...Array.isArray(overrides.reporters) ? overrides.reporters : ["default"],
31
+ jestReporterFile(import.meta.url)
32
+ ],
33
+ // The runner's own total, to check the notes against.
34
+ coverageReporters: [
35
+ ...Array.isArray(overrides.coverageReporters) ? overrides.coverageReporters : ["text"],
36
+ "json"
37
+ ],
38
+ coverageDirectory: path.join(process.env[TOUCH_ENV], "coverage")
39
+ } : {},
23
40
  rootDir,
24
41
  // Never an empty array: Jest would fall back to its default pattern and run every test.
25
42
  testMatch: (specs.length > 0 ? specs : [NO_SPEC_PATTERN]).map((spec) => `<rootDir>/${spec}`),
package/dist/stryker.cjs CHANGED
@@ -511,19 +511,22 @@ function specCandidatesFor(gatePath, settings) {
511
511
  // src/fs.ts
512
512
  var import_node_fs = __toESM(require("fs"), 1);
513
513
  var import_node_path = __toESM(require("path"), 1);
514
- function existsExact(rootDir, relativePath, kind = "file") {
514
+ function existsExact(rootDir, relativePath, kind = "file", listing = /* @__PURE__ */ new Map()) {
515
515
  let current = rootDir;
516
516
  for (const segment of relativePath.split("/")) {
517
517
  if (segment === "" || segment === ".") {
518
518
  continue;
519
519
  }
520
- let entries;
521
- try {
522
- entries = import_node_fs.default.readdirSync(current);
523
- } catch {
524
- return false;
520
+ let entries = listing.get(current);
521
+ if (entries === void 0) {
522
+ try {
523
+ entries = import_node_fs.default.readdirSync(current);
524
+ } catch {
525
+ entries = null;
526
+ }
527
+ listing.set(current, entries);
525
528
  }
526
- if (!entries.includes(segment)) {
529
+ if (entries === null || !entries.includes(segment)) {
527
530
  return false;
528
531
  }
529
532
  current = import_node_path.default.join(current, segment);
@@ -558,9 +561,12 @@ function readManifest(dir) {
558
561
  }
559
562
  throw error;
560
563
  }
564
+ const listing = /* @__PURE__ */ new Map();
561
565
  const gates = parsed.gates.map((gate) => {
562
566
  const specCandidates = specCandidatesFor(gate.path, settings);
563
- const existingSpecs = specCandidates.filter((candidate) => existsExact(absoluteDir, candidate));
567
+ const existingSpecs = specCandidates.filter(
568
+ (candidate) => existsExact(absoluteDir, candidate, "file", listing)
569
+ );
564
570
  return {
565
571
  ...gate,
566
572
  specCandidates,
@@ -6,7 +6,7 @@ interface StrykerGatesOptions {
6
6
  }
7
7
  /**
8
8
  * Stryker's own default is 0: one test runner process for the whole run. Measured on a Jest
9
- * project with three mutants that never end (`Hit limit reached`): from the first of them on
9
+ * project with a few mutants that never end (`Hit limit reached`): from the first of them on
10
10
  * the runner handled about a third as many mutants per minute for the rest of the run, and
11
11
  * mutants that are killed in seconds on their own ran out of time. With a new process every 50
12
12
  * mutants the speed stayed the same to the end.
package/dist/stryker.d.ts CHANGED
@@ -6,7 +6,7 @@ interface StrykerGatesOptions {
6
6
  }
7
7
  /**
8
8
  * Stryker's own default is 0: one test runner process for the whole run. Measured on a Jest
9
- * project with three mutants that never end (`Hit limit reached`): from the first of them on
9
+ * project with a few mutants that never end (`Hit limit reached`): from the first of them on
10
10
  * the runner handled about a third as many mutants per minute for the rest of the run, and
11
11
  * mutants that are killed in seconds on their own ran out of time. With a new process every 50
12
12
  * mutants the speed stayed the same to the end.
package/dist/stryker.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  resolveRootDir
3
- } from "./chunk-4HAJIKDZ.js";
3
+ } from "./chunk-C4R3RDHF.js";
4
4
  import {
5
5
  loadGates
6
- } from "./chunk-F7WBEFIL.js";
6
+ } from "./chunk-XMZ6NBV7.js";
7
7
 
8
8
  // src/stryker.ts
9
9
  var WEAKENING_OPTIONS = ["ignoreStatic", "ignorers"];