@t09tanaka/test-gates 0.4.3 → 0.5.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/CHANGELOG.md +21 -0
- package/README.md +30 -2
- package/dist/cli.js +119 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `selfcheck --jobs` no longer keeps every other control apart because the word `require` appears inside a string, a template, a comment or a regular expression (`'sslmode=require'`, `// pass it to require`). Only code is looked at now. Handing `require` on under another name (`const r = require`) still counts as loading a module whose name is computed.
|
|
8
|
+
|
|
9
|
+
## 0.5.0
|
|
10
|
+
|
|
11
|
+
This release makes the gates quicker to check in CI: `test-gates mutation --shard <index>/<total>` splits the mutation run over parallel jobs, and `selfcheck --jobs` runs more controls side by side. Nothing changes for a run without the new option:
|
|
12
|
+
|
|
13
|
+
- `--shard` deals the gates out in the same way as `selfcheck --shard`. A passing part says nothing about the others, so run every part and make each one a required check.
|
|
14
|
+
- `selfcheck --jobs` judges every control as before. Only the reason that kept a control apart because of an import of `path`, `url`, `crypto` and the like is gone.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `test-gates mutation --shard <index>/<total>` runs the mutants of one part of the gates, so that the mutation run can be split over parallel CI jobs. The assignment is the one of `selfcheck --shard`: the gates are dealt out in turn in the order of `test-gates.json`, so the same `--shard i/n` takes the same gates in both commands. More parts than gates is an error. One passing part is not a guarantee for the whole: run every part (the number of matrix entries has to be `<total>`) and make each one a required check. Unlike `--gate` it is accepted when `CI` is set, and it cannot be combined with `--gate`, `--affected` or `--mutate`.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- `selfcheck --jobs` no longer keeps a control apart because a file its run loads imports `path`, `url`, `querystring`, `string_decoder`, `punycode`, `assert`, `util/types` or `crypto` (with or without `node:`, and the subpaths `path/posix`, `path/win32` and `assert/strict`). They only compute. A use of `crypto.setFips`, `setEngine`, `setDefaultEncoding` or `DEFAULT_ENCODING` still counts. Every other built-in module (`fs`, `os`, `util`, `events`, `buffer`, `timers/promises`, ...) keeps its controls apart as before, and a project cannot vouch for one by its name. `--jobs` is still 1 by default, and `--quick` is still refused when `CI` is set.
|
|
23
|
+
|
|
3
24
|
## 0.4.3
|
|
4
25
|
|
|
5
26
|
This release spares more full runs of `test-gates mutation`: `--affected` keeps telling the gates apart where only some of them read files, a new linked worktree starts from the record of the main worktree, and timeouts on a loaded machine are run again before they fail the run. Three things to know when updating:
|
package/README.md
CHANGED
|
@@ -234,7 +234,7 @@ Static checks, before the gate run. Reports every violation as `file:line: reaso
|
|
|
234
234
|
- The Stryker config does not use `excludedMutations`, `ignoreStatic` or `ignorers`.
|
|
235
235
|
- A Vitest gate run below Vitest 4 does not use the plain v8 coverage provider (since 0.3.0), see [Vitest: v8 or istanbul](#vitest-v8-or-istanbul).
|
|
236
236
|
|
|
237
|
-
### `test-gates mutation [--gate <path>…] [--affected] [-- <stryker arguments>]`
|
|
237
|
+
### `test-gates mutation [--gate <path>… | --shard <index>/<total>] [--affected] [-- <stryker arguments>]`
|
|
238
238
|
|
|
239
239
|
Runs `stryker run <config>` from the project's own `node_modules`, then judges the JSON report (see `mutation-result`). Unknown arguments, and everything after `--`, are handed to Stryker. If Stryker itself fails, the exit code is 1 and nothing is judged. A report from an earlier run is deleted first, so a run that writes none cannot pass on old results.
|
|
240
240
|
|
|
@@ -323,6 +323,34 @@ So a run with `--gate`
|
|
|
323
323
|
|
|
324
324
|
`--gate` is written before `--`, and cannot be combined with a `--mutate` of your own.
|
|
325
325
|
|
|
326
|
+
#### `--shard <index>/<total>`: the mutants of one part of the gates, for parallel CI jobs
|
|
327
|
+
|
|
328
|
+
When the mutation run takes too long for one CI job, split it: `test-gates mutation --shard 2/4` runs the mutants of part 2 of 4 (the index starts at 1) and judges those gates. Unlike `--gate` it is for the final check, so **it is accepted when `CI` is set**. **One part that passes says nothing about the gates of the other parts**: the check is complete only when every part has run and passed.
|
|
329
|
+
|
|
330
|
+
- The gates are dealt out in turn in the order of `test-gates.json`: the gate at position `k` (from 0) belongs to part `k % total + 1`. This is the assignment of `selfcheck --shard` too, so the same `--shard i/n` takes the same gates in both commands. The parts are disjoint and together every gate exactly once.
|
|
331
|
+
- More parts than gates is an error (exit code 2), as in `selfcheck`: a part without a gate would pass without checking anything.
|
|
332
|
+
- The gates of the part are handed to Stryker as `--mutate` and judged; the last line says which part it was (`Only part 2/4 (3 of 11 gate(s)) was run and judged (--shard)`). `--shard 1/1` is a run of every gate, and says so. Every mutant of the gates is run: the handling of an incremental file is the one of a run without `--shard`, which is not what `--gate` does. The timeouts of the part are run again one at a time (`--concurrency 1`) as in any run, and only for the gates of the part.
|
|
333
|
+
- `<index>/<total>` outside `1 <= index <= total`, or not written as two plain numbers, is an error (exit code 2). `--shard` cannot be combined with `--gate`, `--affected` or a `--mutate` of your own, and is written before `--`.
|
|
334
|
+
|
|
335
|
+
A matrix of a hosted CI, in parts:
|
|
336
|
+
|
|
337
|
+
```yaml
|
|
338
|
+
test-gates-mutation:
|
|
339
|
+
runs-on: ubuntu-latest
|
|
340
|
+
strategy:
|
|
341
|
+
fail-fast: false
|
|
342
|
+
matrix:
|
|
343
|
+
part: [1, 2, 3, 4]
|
|
344
|
+
defaults:
|
|
345
|
+
run:
|
|
346
|
+
working-directory: backend
|
|
347
|
+
steps:
|
|
348
|
+
# checkout, setup-node and npm ci as above
|
|
349
|
+
- run: npm run test:gates:mutation -- --shard ${{ matrix.part }}/4
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
The number of parts is written twice, in `matrix.part` and after the `/`: the two have to be the same, or some gates are run by no job. Make every part a required check (one for each entry of the matrix), so that no part can be left out.
|
|
353
|
+
|
|
326
354
|
#### `--affected`: run again only the gates a change can affect
|
|
327
355
|
|
|
328
356
|
Without the option, a change to any file that is compared makes the run one of every mutant. That is the safe answer, but in a pre-push hook that only runs for the subprojects a push touches, it is the answer every time. `test-gates mutation --affected` (`npm run test:gates:mutation -- --affected`) decides gate by gate instead: a gate whose results cannot have changed keeps them, and every mutant of a gate that may have changed is run again. It never relies on Stryker's judgement per mutant, which is what reused the old "Killed" in the measurements above.
|
|
@@ -519,7 +547,7 @@ _Before the runs start_, the gate command and its config must be known, and for
|
|
|
519
547
|
A spec that reaches outside but loads no other gate, which is what the spec of a gate that needs a clock, a file or a package usually is, keeps no control from running next to others; it is left out of the run of its own gate. A file reaches outside when it
|
|
520
548
|
|
|
521
549
|
- mentions `process.`, `globalThis`, `global.` or `import.meta.env`; the clock or a wait (`Date.now`, `new Date()`, `performance.now`, `setTimeout`, `setInterval`, `setImmediate`, `waitFor`, `waitUntil`, `expect.poll`, `retryTimes`, `retry:`); the network (`fetch(`, `WebSocket`, `XMLHttpRequest`); or `navigator`, `Atomics`, `WeakRef`, `FinalizationRegistry`, `eval(`, `Function(`,
|
|
522
|
-
- imports a built-in module of Node.js
|
|
550
|
+
- imports a built-in module of Node.js other than `path`, `url`, `querystring`, `string_decoder`, `punycode`, `assert`, `util/types` and `crypto` (these only compute, and a use of `crypto.setFips`, `setEngine`, `setDefaultEncoding` or `DEFAULT_ENCODING` still counts; a project cannot vouch for another built-in module by its name): with Jest the spec files of one worker share the objects of the built-in modules, so a setting that one spec changes there is seen by the next, and which spec comes next depends on Jest's cache of run times,
|
|
523
551
|
- imports a package other than the runner's (`vitest`, `@jest/globals`). Packages are not looked into. With `settings.imports.mode: "allowlist"`, the packages that `settings.imports.allow` lists by name are taken too: the project already vouches for them as imports of a gate,
|
|
524
552
|
- loads a module whose name is computed.
|
|
525
553
|
|
package/dist/cli.js
CHANGED
|
@@ -28,7 +28,7 @@ function parseShard(value) {
|
|
|
28
28
|
}
|
|
29
29
|
var OPTIONS_BY_COMMAND = {
|
|
30
30
|
check: ["--dir"],
|
|
31
|
-
mutation: ["--dir", "--gate", "--affected"],
|
|
31
|
+
mutation: ["--dir", "--gate", "--shard", "--affected"],
|
|
32
32
|
"mutation-result": ["--dir", "--report"],
|
|
33
33
|
selfcheck: ["--dir", "--all", "--first", "--gate", "--shard", "--quick", "--jobs"],
|
|
34
34
|
lcov: ["--dir", "--file"],
|
|
@@ -146,13 +146,18 @@ function parseArgs(argv) {
|
|
|
146
146
|
"--affected and --gate cannot be combined: --gate runs the gates it names, --affected decides which gates run"
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
|
+
if (parsed.affected && parsed.shard !== null) {
|
|
150
|
+
throw new UsageError(
|
|
151
|
+
"--affected and --shard cannot be combined: --shard runs every mutant of its gates, --affected decides which gates run"
|
|
152
|
+
);
|
|
153
|
+
}
|
|
149
154
|
const misplaced = parsed.rest.find((arg) => /^--(gate|shard|quick|jobs)(=|$)/.test(arg));
|
|
150
155
|
if (parsed.command === "selfcheck" && misplaced !== void 0) {
|
|
151
156
|
throw new UsageError(
|
|
152
157
|
`${misplaced.split("=")[0]} after "--" would go to the gate command. Write it before "--"`
|
|
153
158
|
);
|
|
154
159
|
}
|
|
155
|
-
const forStryker = parsed.rest.find((arg) => /^--(gate|affected)(=|$)/.test(arg));
|
|
160
|
+
const forStryker = parsed.rest.find((arg) => /^--(gate|shard|affected)(=|$)/.test(arg));
|
|
156
161
|
if (forStryker !== void 0) {
|
|
157
162
|
throw new UsageError(
|
|
158
163
|
`${forStryker.split("=")[0]} after "--" would go to Stryker. Write it before "--"`
|
|
@@ -1083,6 +1088,12 @@ function gateRunRefused(env) {
|
|
|
1083
1088
|
function affectedRefused(env) {
|
|
1084
1089
|
return inCi(env) ? "CI is set, and there every mutant is run (the final check)" : null;
|
|
1085
1090
|
}
|
|
1091
|
+
function shardRunCaveat(shard, judged, all) {
|
|
1092
|
+
if (shard.total === 1) {
|
|
1093
|
+
return `. All ${all} gate(s) were run and judged (--shard 1/1 is the only part)`;
|
|
1094
|
+
}
|
|
1095
|
+
return `. Only part ${shard.index}/${shard.total} (${judged} of ${all} gate(s)) was run and judged (--shard): every part has to pass for the final check`;
|
|
1096
|
+
}
|
|
1086
1097
|
function gateRunCaveat(judged, all) {
|
|
1087
1098
|
return `. Only ${judged} of ${all} gate(s) were run and judged (--gate), and a change in one gate can make a mutant of another survive: run without --gate for the final check`;
|
|
1088
1099
|
}
|
|
@@ -4326,10 +4337,10 @@ import path9 from "path";
|
|
|
4326
4337
|
function reportPathOf(manifest, override) {
|
|
4327
4338
|
return path9.resolve(manifest.dir, override ?? manifest.settings.strykerReportFile);
|
|
4328
4339
|
}
|
|
4329
|
-
function runMutationResult(dir, reportOverride, io2, only = [], seen = () => void 0, retried = null) {
|
|
4340
|
+
function runMutationResult(dir, reportOverride, io2, only = [], seen = () => void 0, retried = null, shard = null) {
|
|
4330
4341
|
const manifest = readManifest(dir);
|
|
4331
4342
|
const gates = only.length === 0 ? manifest.gates : manifest.gates.filter((gate) => only.includes(gate.path));
|
|
4332
|
-
const caveat = only.length === 0 ? "" : gateRunCaveat(gates.length, manifest.gates.length);
|
|
4343
|
+
const caveat = only.length === 0 ? "" : shard === null ? gateRunCaveat(gates.length, manifest.gates.length) : shardRunCaveat(shard, gates.length, manifest.gates.length);
|
|
4333
4344
|
const reportPath = reportPathOf(manifest, reportOverride);
|
|
4334
4345
|
const shown = path9.relative(manifest.dir, reportPath) || reportPath;
|
|
4335
4346
|
let report;
|
|
@@ -4879,11 +4890,11 @@ function guardIncremental(manifest, config, strykerArgs, part, affected, found)
|
|
|
4879
4890
|
}
|
|
4880
4891
|
}
|
|
4881
4892
|
}
|
|
4882
|
-
function runMutation(dir, strykerArgs, io2, only = [], affected = false) {
|
|
4893
|
+
function runMutation(dir, strykerArgs, io2, only = [], affected = false, shard = null) {
|
|
4883
4894
|
const part = only.length > 0;
|
|
4884
|
-
if (affected && strykerArgs.some((arg) => /^(?:--mutate|-m)(?:=|$)/.test(arg))) {
|
|
4895
|
+
if ((affected || shard !== null) && strykerArgs.some((arg) => /^(?:--mutate|-m)(?:=|$)/.test(arg))) {
|
|
4885
4896
|
throw new UsageError(
|
|
4886
|
-
"--affected and --mutate cannot be combined: --affected sets --mutate itself
|
|
4897
|
+
`${affected ? "--affected" : "--shard"} and --mutate cannot be combined: ${affected ? "--affected" : "--shard"} sets --mutate itself`
|
|
4887
4898
|
);
|
|
4888
4899
|
}
|
|
4889
4900
|
if (part) {
|
|
@@ -4900,11 +4911,12 @@ function runMutation(dir, strykerArgs, io2, only = [], affected = false) {
|
|
|
4900
4911
|
gates: manifest.gates.map((gate) => gate.path),
|
|
4901
4912
|
mode: "all",
|
|
4902
4913
|
only,
|
|
4903
|
-
shard
|
|
4914
|
+
shard
|
|
4904
4915
|
});
|
|
4905
4916
|
if ("error" in chosen) {
|
|
4906
4917
|
throw new UsageError(chosen.error);
|
|
4907
4918
|
}
|
|
4919
|
+
const shardGates = shard === null ? null : chosen.selected;
|
|
4908
4920
|
const config = findStrykerConfig(manifest);
|
|
4909
4921
|
if (config === null) {
|
|
4910
4922
|
throw new ManifestError(
|
|
@@ -4930,7 +4942,8 @@ function runMutation(dir, strykerArgs, io2, only = [], affected = false) {
|
|
|
4930
4942
|
}
|
|
4931
4943
|
removed.forEach((line) => io2.out(line));
|
|
4932
4944
|
const guardArgs = guard.force === null && guard.mutate === null ? [] : ["--force"];
|
|
4933
|
-
const partArgs = part ? ["--mutate", chosen.selected.join(",")] : guard.mutate !== null ? ["--mutate", guard.mutate.join(",")] : [];
|
|
4945
|
+
const partArgs = part || shardGates !== null ? ["--mutate", (shardGates ?? chosen.selected).join(",")] : guard.mutate !== null ? ["--mutate", guard.mutate.join(",")] : [];
|
|
4946
|
+
const judged = part ? chosen.selected : shardGates ?? [];
|
|
4934
4947
|
const unmark = markRun(manifest.dir, guard.temp);
|
|
4935
4948
|
let retried;
|
|
4936
4949
|
try {
|
|
@@ -4954,24 +4967,25 @@ function runMutation(dir, strykerArgs, io2, only = [], affected = false) {
|
|
|
4954
4967
|
[config, ...strykerArgs],
|
|
4955
4968
|
manifest,
|
|
4956
4969
|
reportFile,
|
|
4957
|
-
|
|
4970
|
+
judged.length > 0 ? manifest.gates.filter((gate) => judged.includes(gate.path)) : manifest.gates,
|
|
4958
4971
|
io2
|
|
4959
4972
|
);
|
|
4960
4973
|
} finally {
|
|
4961
4974
|
unmark();
|
|
4962
4975
|
}
|
|
4963
|
-
let
|
|
4976
|
+
let lastSeen = null;
|
|
4964
4977
|
const status = runMutationResult(
|
|
4965
4978
|
dir,
|
|
4966
4979
|
null,
|
|
4967
4980
|
io2,
|
|
4968
|
-
|
|
4981
|
+
judged,
|
|
4969
4982
|
(seen) => {
|
|
4970
|
-
|
|
4983
|
+
lastSeen = seen;
|
|
4971
4984
|
},
|
|
4972
|
-
retried
|
|
4985
|
+
retried,
|
|
4986
|
+
shard
|
|
4973
4987
|
);
|
|
4974
|
-
guard.record(
|
|
4988
|
+
guard.record(lastSeen);
|
|
4975
4989
|
if (part) {
|
|
4976
4990
|
fs10.rmSync(reportFile, { force: true });
|
|
4977
4991
|
}
|
|
@@ -5420,6 +5434,11 @@ var REACHES = [
|
|
|
5420
5434
|
"WebSocket",
|
|
5421
5435
|
"XMLHttpRequest",
|
|
5422
5436
|
"navigator",
|
|
5437
|
+
// What crypto keeps as a setting of its module, for every spec file of a worker after it.
|
|
5438
|
+
"setFips",
|
|
5439
|
+
"setEngine",
|
|
5440
|
+
"setDefaultEncoding",
|
|
5441
|
+
"DEFAULT_ENCODING",
|
|
5423
5442
|
"Atomics",
|
|
5424
5443
|
"WeakRef",
|
|
5425
5444
|
"FinalizationRegistry",
|
|
@@ -5439,9 +5458,86 @@ function withoutStrings(source) {
|
|
|
5439
5458
|
)
|
|
5440
5459
|
);
|
|
5441
5460
|
}
|
|
5461
|
+
var BEFORE_REGEX = /(?:^|[(,=:[!&|?{};+\-*%<>~^]|\b(?:return|typeof|case|in|of|void|delete|throw|new|yield|await))\s*$/;
|
|
5462
|
+
var COMMENT_OR_STRING = /\/\/[^\n]*|\/\*[\s\S]*?(?:\*\/|$)|(['"])(?:\\.|(?!\1)[^\\\n])*\1?/y;
|
|
5463
|
+
var REGEX = /\/(?:\\.|\[(?:\\.|[^\]\\\n])*\]?|[^/\\\n[])*\/?/y;
|
|
5464
|
+
var TEMPLATE_TEXT = /(?:\\[\s\S]|[^`\\$]|\$(?!\{))*/y;
|
|
5465
|
+
function withoutLiterals(source) {
|
|
5466
|
+
let out = "";
|
|
5467
|
+
let i = 0;
|
|
5468
|
+
const blank = (text) => text.replace(/[^\n]/g, " ");
|
|
5469
|
+
const enclosed = (literal) => {
|
|
5470
|
+
const closed = literal.length > 1 && literal.endsWith(literal.charAt(0));
|
|
5471
|
+
return literal.charAt(0) + blank(literal.slice(1, closed ? -1 : void 0)) + (closed ? literal.charAt(0) : "");
|
|
5472
|
+
};
|
|
5473
|
+
const take = (pattern) => {
|
|
5474
|
+
pattern.lastIndex = i;
|
|
5475
|
+
const found = pattern.exec(source)?.[0] ?? null;
|
|
5476
|
+
if (found !== null) {
|
|
5477
|
+
i += found.length;
|
|
5478
|
+
}
|
|
5479
|
+
return found;
|
|
5480
|
+
};
|
|
5481
|
+
const code = (inBraces) => {
|
|
5482
|
+
while (i < source.length) {
|
|
5483
|
+
const c = source[i];
|
|
5484
|
+
const literal = take(COMMENT_OR_STRING);
|
|
5485
|
+
if (literal !== null) {
|
|
5486
|
+
out += literal.startsWith("/") ? blank(literal) : enclosed(literal);
|
|
5487
|
+
} else if (c === "/" && BEFORE_REGEX.test(out)) {
|
|
5488
|
+
out += enclosed(take(REGEX));
|
|
5489
|
+
} else if (c === "`") {
|
|
5490
|
+
i += 1;
|
|
5491
|
+
out += "`";
|
|
5492
|
+
template();
|
|
5493
|
+
} else if (c === "}" && inBraces) {
|
|
5494
|
+
return;
|
|
5495
|
+
} else if (c === "{" && inBraces) {
|
|
5496
|
+
braces("{");
|
|
5497
|
+
} else {
|
|
5498
|
+
out += c;
|
|
5499
|
+
i += 1;
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
};
|
|
5503
|
+
const braces = (open) => {
|
|
5504
|
+
i += open.length;
|
|
5505
|
+
out += open;
|
|
5506
|
+
code(true);
|
|
5507
|
+
if (i < source.length) {
|
|
5508
|
+
i += 1;
|
|
5509
|
+
out += "}";
|
|
5510
|
+
}
|
|
5511
|
+
};
|
|
5512
|
+
const template = () => {
|
|
5513
|
+
out += blank(take(TEMPLATE_TEXT));
|
|
5514
|
+
if (source.startsWith("${", i)) {
|
|
5515
|
+
braces("${");
|
|
5516
|
+
template();
|
|
5517
|
+
} else if (i < source.length) {
|
|
5518
|
+
i += 1;
|
|
5519
|
+
out += "`";
|
|
5520
|
+
}
|
|
5521
|
+
};
|
|
5522
|
+
code(false);
|
|
5523
|
+
return out;
|
|
5524
|
+
}
|
|
5525
|
+
var PURE_BUILTINS = [
|
|
5526
|
+
"assert",
|
|
5527
|
+
"assert/strict",
|
|
5528
|
+
"crypto",
|
|
5529
|
+
"path",
|
|
5530
|
+
"path/posix",
|
|
5531
|
+
"path/win32",
|
|
5532
|
+
"punycode",
|
|
5533
|
+
"querystring",
|
|
5534
|
+
"string_decoder",
|
|
5535
|
+
"url",
|
|
5536
|
+
"util/types"
|
|
5537
|
+
];
|
|
5442
5538
|
var RUNNER_PACKAGES = ["vitest", "@jest/globals"];
|
|
5443
5539
|
function reachesOutside(file, trusted) {
|
|
5444
|
-
const dense = file.source.replace(/\s/g, "");
|
|
5540
|
+
const dense = withoutLiterals(file.source).replace(/\s/g, "");
|
|
5445
5541
|
const code = withoutStrings(file.source).replace(/\s/g, "");
|
|
5446
5542
|
const mark = REACHES.find((text) => code.includes(text));
|
|
5447
5543
|
if (mark !== void 0) {
|
|
@@ -5452,7 +5548,9 @@ function reachesOutside(file, trusted) {
|
|
|
5452
5548
|
}
|
|
5453
5549
|
for (const { specifier, builtin } of file.imports) {
|
|
5454
5550
|
if (builtin) {
|
|
5455
|
-
|
|
5551
|
+
if (!PURE_BUILTINS.includes(specifier.replace(/^node:/, ""))) {
|
|
5552
|
+
return `imports ${specifier}`;
|
|
5553
|
+
}
|
|
5456
5554
|
} else if (![...RUNNER_PACKAGES, ...trusted].some(
|
|
5457
5555
|
(name) => specifier === name || specifier.startsWith(`${name}/`)
|
|
5458
5556
|
)) {
|
|
@@ -6040,7 +6138,9 @@ Options:
|
|
|
6040
6138
|
--affected mutation: run again only the gates that a change since the last
|
|
6041
6139
|
run can affect, and reuse the results of the others. Every
|
|
6042
6140
|
mutant is run when CI is set
|
|
6043
|
-
--shard <index>/<total> selfcheck: one of <total> parts of the gates, for parallel CI
|
|
6141
|
+
--shard <index>/<total> selfcheck, mutation: one of <total> parts of the gates, for parallel CI
|
|
6142
|
+
jobs. For mutation the gates are sorted and dealt out in turn; every
|
|
6143
|
+
part has to run. Accepted when CI is set
|
|
6044
6144
|
--jobs <n> selfcheck: run <n> negative controls at the same time. One that does
|
|
6045
6145
|
not hold cleanly there is run again on its own
|
|
6046
6146
|
--quick selfcheck: one run for the gates only their own spec touches.
|
|
@@ -6075,7 +6175,7 @@ function main(argv) {
|
|
|
6075
6175
|
case "doctor":
|
|
6076
6176
|
return runDoctor(args.dir, io);
|
|
6077
6177
|
case "mutation":
|
|
6078
|
-
return runMutation(args.dir, args.rest, io, args.gates, args.affected);
|
|
6178
|
+
return runMutation(args.dir, args.rest, io, args.gates, args.affected, args.shard);
|
|
6079
6179
|
case "mutation-result":
|
|
6080
6180
|
return runMutationResult(args.dir, args.report, io);
|
|
6081
6181
|
case "selfcheck":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t09tanaka/test-gates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Test gates for the code that must not be wrong: per-file 100% coverage as the floor, mutation testing with a per-mutant allow list as the real check.",
|
|
5
5
|
"author": "t09tanaka",
|
|
6
6
|
"license": "MIT",
|