@t09tanaka/test-gates 0.3.3 → 0.4.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/CHANGELOG.md +44 -0
- package/README.md +290 -17
- package/dist/{chunk-F7WBEFIL.js → chunk-AK2NTFQO.js} +39 -10
- package/dist/chunk-TOKHAUFX.js +163 -0
- package/dist/cli.js +3129 -501
- package/dist/index.cjs +37 -12
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +4 -3
- package/dist/jest-touch-reporter.cjs +129 -0
- package/dist/jest-touch-reporter.d.cts +19 -0
- package/dist/jest-touch-reporter.d.ts +19 -0
- package/dist/jest-touch-reporter.js +48 -0
- package/dist/jest.cjs +70 -12
- package/dist/jest.js +19 -2
- package/dist/stryker.cjs +29 -10
- package/dist/stryker.d.cts +15 -1
- package/dist/stryker.d.ts +15 -1
- package/dist/stryker.js +10 -3
- package/dist/vitest.cjs +151 -12
- package/dist/vitest.js +22 -4
- package/package.json +7 -2
- /package/dist/{chunk-4HAJIKDZ.js → chunk-C4R3RDHF.js} +0 -0
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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(
|
|
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
|
|
644
|
-
return manifest.gates.map((gate) => gate.spec).filter((spec) => spec !== null && spec
|
|
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.d.cts
CHANGED
|
@@ -233,6 +233,10 @@ interface ReportMutant {
|
|
|
233
233
|
replacement?: string;
|
|
234
234
|
status: string;
|
|
235
235
|
location: ReportLocation;
|
|
236
|
+
/** Ids of the tests that ran the mutated code (`coverageAnalysis: perTest`). */
|
|
237
|
+
coveredBy?: string[];
|
|
238
|
+
/** Ids of the tests that failed with the mutant. */
|
|
239
|
+
killedBy?: string[];
|
|
236
240
|
}
|
|
237
241
|
interface ReportFile {
|
|
238
242
|
source?: string;
|
|
@@ -241,6 +245,12 @@ interface ReportFile {
|
|
|
241
245
|
/** The part of the mutation-testing-elements JSON report that the verdict depends on. */
|
|
242
246
|
interface MutationReport {
|
|
243
247
|
files?: Record<string, ReportFile>;
|
|
248
|
+
/** The spec files, with the ids of their tests (read by `mutation --affected`). */
|
|
249
|
+
testFiles?: Record<string, {
|
|
250
|
+
tests?: Array<{
|
|
251
|
+
id: string;
|
|
252
|
+
}>;
|
|
253
|
+
}>;
|
|
244
254
|
}
|
|
245
255
|
/** A survivor that no allowance covers. `allowance` can be pasted into `equivalentMutants`. */
|
|
246
256
|
interface UnallowedSurvivor {
|
package/dist/index.d.ts
CHANGED
|
@@ -233,6 +233,10 @@ interface ReportMutant {
|
|
|
233
233
|
replacement?: string;
|
|
234
234
|
status: string;
|
|
235
235
|
location: ReportLocation;
|
|
236
|
+
/** Ids of the tests that ran the mutated code (`coverageAnalysis: perTest`). */
|
|
237
|
+
coveredBy?: string[];
|
|
238
|
+
/** Ids of the tests that failed with the mutant. */
|
|
239
|
+
killedBy?: string[];
|
|
236
240
|
}
|
|
237
241
|
interface ReportFile {
|
|
238
242
|
source?: string;
|
|
@@ -241,6 +245,12 @@ interface ReportFile {
|
|
|
241
245
|
/** The part of the mutation-testing-elements JSON report that the verdict depends on. */
|
|
242
246
|
interface MutationReport {
|
|
243
247
|
files?: Record<string, ReportFile>;
|
|
248
|
+
/** The spec files, with the ids of their tests (read by `mutation --affected`). */
|
|
249
|
+
testFiles?: Record<string, {
|
|
250
|
+
tests?: Array<{
|
|
251
|
+
id: string;
|
|
252
|
+
}>;
|
|
253
|
+
}>;
|
|
244
254
|
}
|
|
245
255
|
/** A survivor that no allowance covers. `allowance` can be pasted into `equivalentMutants`. */
|
|
246
256
|
interface UnallowedSurvivor {
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
readText,
|
|
21
21
|
resolveSettings,
|
|
22
22
|
validateEquivalentMutants
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-AK2NTFQO.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
|
-
|
|
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
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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(
|
|
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
|
|
594
|
-
return manifest.gates.map((gate) => gate.spec).filter((spec) => spec !== null && spec
|
|
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) => [
|
|
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-
|
|
8
|
+
} from "./chunk-C4R3RDHF.js";
|
|
5
9
|
import {
|
|
6
10
|
NO_SPEC_PATTERN,
|
|
7
11
|
gateSpecs,
|
|
8
12
|
loadGates
|
|
9
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-AK2NTFQO.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
|
@@ -31,9 +31,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var stryker_exports = {};
|
|
32
32
|
__export(stryker_exports, {
|
|
33
33
|
DEFAULT_MAX_TEST_RUNNER_REUSE: () => DEFAULT_MAX_TEST_RUNNER_REUSE,
|
|
34
|
-
createStrykerGatesConfig: () => createStrykerGatesConfig
|
|
34
|
+
createStrykerGatesConfig: () => createStrykerGatesConfig,
|
|
35
|
+
defaultConcurrency: () => defaultConcurrency
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(stryker_exports);
|
|
38
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
39
|
+
|
|
40
|
+
// src/core/selfcheck.ts
|
|
41
|
+
function inCi(env) {
|
|
42
|
+
const ci = (env.CI ?? "").trim().toLowerCase();
|
|
43
|
+
return ci !== "" && ci !== "false" && ci !== "0";
|
|
44
|
+
}
|
|
37
45
|
|
|
38
46
|
// src/load.ts
|
|
39
47
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -511,19 +519,22 @@ function specCandidatesFor(gatePath, settings) {
|
|
|
511
519
|
// src/fs.ts
|
|
512
520
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
513
521
|
var import_node_path = __toESM(require("path"), 1);
|
|
514
|
-
function existsExact(rootDir, relativePath, kind = "file") {
|
|
522
|
+
function existsExact(rootDir, relativePath, kind = "file", listing = /* @__PURE__ */ new Map()) {
|
|
515
523
|
let current = rootDir;
|
|
516
524
|
for (const segment of relativePath.split("/")) {
|
|
517
525
|
if (segment === "" || segment === ".") {
|
|
518
526
|
continue;
|
|
519
527
|
}
|
|
520
|
-
let entries;
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
528
|
+
let entries = listing.get(current);
|
|
529
|
+
if (entries === void 0) {
|
|
530
|
+
try {
|
|
531
|
+
entries = import_node_fs.default.readdirSync(current);
|
|
532
|
+
} catch {
|
|
533
|
+
entries = null;
|
|
534
|
+
}
|
|
535
|
+
listing.set(current, entries);
|
|
525
536
|
}
|
|
526
|
-
if (!entries.includes(segment)) {
|
|
537
|
+
if (entries === null || !entries.includes(segment)) {
|
|
527
538
|
return false;
|
|
528
539
|
}
|
|
529
540
|
current = import_node_path.default.join(current, segment);
|
|
@@ -558,9 +569,12 @@ function readManifest(dir) {
|
|
|
558
569
|
}
|
|
559
570
|
throw error;
|
|
560
571
|
}
|
|
572
|
+
const listing = /* @__PURE__ */ new Map();
|
|
561
573
|
const gates = parsed.gates.map((gate) => {
|
|
562
574
|
const specCandidates = specCandidatesFor(gate.path, settings);
|
|
563
|
-
const existingSpecs = specCandidates.filter(
|
|
575
|
+
const existingSpecs = specCandidates.filter(
|
|
576
|
+
(candidate) => existsExact(absoluteDir, candidate, "file", listing)
|
|
577
|
+
);
|
|
564
578
|
return {
|
|
565
579
|
...gate,
|
|
566
580
|
specCandidates,
|
|
@@ -603,6 +617,9 @@ function resolveRootDir(rootDir) {
|
|
|
603
617
|
// src/stryker.ts
|
|
604
618
|
var WEAKENING_OPTIONS = ["ignoreStatic", "ignorers"];
|
|
605
619
|
var DEFAULT_MAX_TEST_RUNNER_REUSE = 50;
|
|
620
|
+
function defaultConcurrency(parallelism, ci) {
|
|
621
|
+
return ci ? 2 : Math.min(8, Math.max(1, Math.floor(parallelism / 2)));
|
|
622
|
+
}
|
|
606
623
|
function createStrykerGatesConfig(options = {}) {
|
|
607
624
|
const { rootDir: rootDirOption, ...overrides } = options;
|
|
608
625
|
for (const option of WEAKENING_OPTIONS) {
|
|
@@ -628,6 +645,7 @@ function createStrykerGatesConfig(options = {}) {
|
|
|
628
645
|
incrementalFile: "reports/stryker-incremental.json",
|
|
629
646
|
tempDirName: ".stryker-tmp",
|
|
630
647
|
maxTestRunnerReuse: DEFAULT_MAX_TEST_RUNNER_REUSE,
|
|
648
|
+
concurrency: defaultConcurrency(import_node_os.default.availableParallelism(), inCi(process.env)),
|
|
631
649
|
clearTextReporter: { logTests: false, reportTests: false },
|
|
632
650
|
...overrides,
|
|
633
651
|
mutate: manifest.gates.map((gate) => gate.path),
|
|
@@ -639,5 +657,6 @@ function createStrykerGatesConfig(options = {}) {
|
|
|
639
657
|
// Annotate the CommonJS export names for ESM import in node:
|
|
640
658
|
0 && (module.exports = {
|
|
641
659
|
DEFAULT_MAX_TEST_RUNNER_REUSE,
|
|
642
|
-
createStrykerGatesConfig
|
|
660
|
+
createStrykerGatesConfig,
|
|
661
|
+
defaultConcurrency
|
|
643
662
|
});
|