@t09tanaka/test-gates 0.3.1 → 0.3.2

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2
4
+
5
+ ### Changed
6
+
7
+ - `createStrykerGatesConfig` sets Stryker's `maxTestRunnerReuse` to 50 unless the project passes its own (Stryker's default is 0: one test runner process for the whole run). In a project with mutants that never end, the runner became about three times slower from the first of them on, and mutants that are killed in seconds on their own timed out. Starting a new process every 50 mutants kept the speed: a full run of 781 mutants went from 14 to 18 minutes with stray timeouts to under 7 minutes with none.
8
+ - `timeoutMS` is no longer set by the helper; Stryker's default (5000) applies again. The 30000 of 0.3.1 was the wrong fix. It rested on the idea that a restarted runner makes the next mutant slow, when in fact a runner that is not restarted stays slow. With 30000 the same full run took 47 minutes and still failed, and mutants that exhaust the heap crashed the runner and were reported as `RuntimeError` instead of `Timeout`.
9
+
10
+ A project that set `timeoutMS` itself to work around stray timeouts can remove it.
11
+
3
12
  ## 0.3.1
4
13
 
5
14
  ### Changed
package/README.md CHANGED
@@ -280,7 +280,9 @@ How an `expectedTimeouts` entry is judged:
280
280
 
281
281
  When the run fails on a timeout, first run again with less load (`npm run test:gates:mutation -- --concurrency 1`). Add an entry only for a mutant that can never finish.
282
282
 
283
- `createStrykerGatesConfig` sets Stryker's `timeoutMS` to 30000 (since 0.3.1; Stryker's own default is 5000). After a mutant that really never ends, Stryker starts a new test runner, and with 5000 the mutant that comes next can run out of time while that runner is still starting: in one project with 11 such mutants, 2 to 7 others timed out on every full run, a different set each time, and none in a run with 30000. A longer limit hides nothing, because a timeout that is not listed fails the run anyway. The price is the wait for each mutant that hangs without tripping Stryker's loop counter. Pass `timeoutMS` to the helper to change it.
283
+ `createStrykerGatesConfig` sets Stryker's `maxTestRunnerReuse` to 50 (since 0.3.2; Stryker's own default is 0, one test runner process for the whole run). A mutant that would be killed can also time out for a reason that has nothing to do with the mutant: in a Jest project with three mutants that never end, the test runner handled about a third as many mutants per minute from the first of them on, for the rest of the run. Mutants that are killed in seconds on their own then timed out, a different set on each full run, and the run took 14 to 18 minutes. With a new process every 50 mutants the speed stayed the same to the end, nothing timed out that should not, and the run took under 7 minutes. Why the runner slows down was not found; the restart only cuts it short. Pass `maxTestRunnerReuse` to the helper to change it.
284
+
285
+ 0.3.1 set `timeoutMS` to 30000 instead. That was the wrong fix and 0.3.2 takes it back: the longer limit let most slow mutants finish, but the run above took 47 minutes, and in another project mutants that exhaust the heap crashed the test runner before the limit and came back as `RuntimeError`, which is only counted, not failed. `timeoutMS` is Stryker's default (5000) again unless the project sets it.
284
286
 
285
287
  ### `test-gates selfcheck [--all | --first] [-- <gate command>]`
286
288
 
@@ -462,7 +464,7 @@ Why these choices:
462
464
  - Jest measures with istanbul (`babel`), because the v8 provider does not count the untaken side of an `if` without `else`.
463
465
  - Jest fails on a `coverageThreshold` path key that matches no file, so a typo in `test-gates.json` cannot pass. Vitest does the opposite: a glob or path key in `thresholds` that matches nothing passes. The Vitest helper therefore narrows `coverage.include` to the gates and uses `perFile`.
464
466
  - Stryker never fails on its own score (`break: null`). The verdict comes from `test-gates mutation`, which knows the allow list.
465
- - The Stryker helper also sets defaults that can be overridden: `coverageAnalysis: 'perTest'`, `incremental` with its file under `reports/`, and `timeoutMS: 30000` (see [Timeouts](#timeouts)).
467
+ - The Stryker helper also sets defaults that can be overridden: `coverageAnalysis: 'perTest'`, `incremental` with its file under `reports/`, and `maxTestRunnerReuse: 50` (see [Timeouts](#timeouts)).
466
468
 
467
469
  `loadGates(dir)` returns the validated manifest: `gates` (each with its resolved `spec`), `candidates` and the resolved `settings`. It throws when the manifest has any problem.
468
470
 
package/dist/stryker.cjs CHANGED
@@ -30,7 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/stryker.ts
31
31
  var stryker_exports = {};
32
32
  __export(stryker_exports, {
33
- DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
33
+ DEFAULT_MAX_TEST_RUNNER_REUSE: () => DEFAULT_MAX_TEST_RUNNER_REUSE,
34
34
  createStrykerGatesConfig: () => createStrykerGatesConfig
35
35
  });
36
36
  module.exports = __toCommonJS(stryker_exports);
@@ -602,7 +602,7 @@ function resolveRootDir(rootDir) {
602
602
 
603
603
  // src/stryker.ts
604
604
  var WEAKENING_OPTIONS = ["ignoreStatic", "ignorers"];
605
- var DEFAULT_TIMEOUT_MS = 3e4;
605
+ var DEFAULT_MAX_TEST_RUNNER_REUSE = 50;
606
606
  function createStrykerGatesConfig(options = {}) {
607
607
  const { rootDir: rootDirOption, ...overrides } = options;
608
608
  for (const option of WEAKENING_OPTIONS) {
@@ -627,7 +627,7 @@ function createStrykerGatesConfig(options = {}) {
627
627
  incremental: true,
628
628
  incrementalFile: "reports/stryker-incremental.json",
629
629
  tempDirName: ".stryker-tmp",
630
- timeoutMS: DEFAULT_TIMEOUT_MS,
630
+ maxTestRunnerReuse: DEFAULT_MAX_TEST_RUNNER_REUSE,
631
631
  clearTextReporter: { logTests: false, reportTests: false },
632
632
  ...overrides,
633
633
  mutate: manifest.gates.map((gate) => gate.path),
@@ -638,6 +638,6 @@ function createStrykerGatesConfig(options = {}) {
638
638
  }
639
639
  // Annotate the CommonJS export names for ESM import in node:
640
640
  0 && (module.exports = {
641
- DEFAULT_TIMEOUT_MS,
641
+ DEFAULT_MAX_TEST_RUNNER_REUSE,
642
642
  createStrykerGatesConfig
643
643
  });
@@ -5,11 +5,13 @@ interface StrykerGatesOptions {
5
5
  [option: string]: unknown;
6
6
  }
7
7
  /**
8
- * Stryker's own default is 5000. After a mutant that really never ends, Stryker starts a new
9
- * test runner, and the next mutant then pays that start-up inside its time limit: with 5000 a
10
- * mutant that would be killed is reported as `Timeout`, which fails the run since 0.3.0.
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
10
+ * the runner handled about a third as many mutants per minute for the rest of the run, and
11
+ * mutants that are killed in seconds on their own ran out of time. With a new process every 50
12
+ * mutants the speed stayed the same to the end.
11
13
  */
12
- declare const DEFAULT_TIMEOUT_MS = 30000;
14
+ declare const DEFAULT_MAX_TEST_RUNNER_REUSE = 50;
13
15
  /**
14
16
  * Stryker config for the gates: mutate exactly the gates, never fail on the score
15
17
  * (`thresholds.break: null`), and write the JSON report that `test-gates mutation` judges.
@@ -19,4 +21,4 @@ declare const DEFAULT_TIMEOUT_MS = 30000;
19
21
  */
20
22
  declare function createStrykerGatesConfig(options?: StrykerGatesOptions): Record<string, unknown>;
21
23
 
22
- export { DEFAULT_TIMEOUT_MS, type StrykerGatesOptions, createStrykerGatesConfig };
24
+ export { DEFAULT_MAX_TEST_RUNNER_REUSE, type StrykerGatesOptions, createStrykerGatesConfig };
package/dist/stryker.d.ts CHANGED
@@ -5,11 +5,13 @@ interface StrykerGatesOptions {
5
5
  [option: string]: unknown;
6
6
  }
7
7
  /**
8
- * Stryker's own default is 5000. After a mutant that really never ends, Stryker starts a new
9
- * test runner, and the next mutant then pays that start-up inside its time limit: with 5000 a
10
- * mutant that would be killed is reported as `Timeout`, which fails the run since 0.3.0.
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
10
+ * the runner handled about a third as many mutants per minute for the rest of the run, and
11
+ * mutants that are killed in seconds on their own ran out of time. With a new process every 50
12
+ * mutants the speed stayed the same to the end.
11
13
  */
12
- declare const DEFAULT_TIMEOUT_MS = 30000;
14
+ declare const DEFAULT_MAX_TEST_RUNNER_REUSE = 50;
13
15
  /**
14
16
  * Stryker config for the gates: mutate exactly the gates, never fail on the score
15
17
  * (`thresholds.break: null`), and write the JSON report that `test-gates mutation` judges.
@@ -19,4 +21,4 @@ declare const DEFAULT_TIMEOUT_MS = 30000;
19
21
  */
20
22
  declare function createStrykerGatesConfig(options?: StrykerGatesOptions): Record<string, unknown>;
21
23
 
22
- export { DEFAULT_TIMEOUT_MS, type StrykerGatesOptions, createStrykerGatesConfig };
24
+ export { DEFAULT_MAX_TEST_RUNNER_REUSE, type StrykerGatesOptions, createStrykerGatesConfig };
package/dist/stryker.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
 
8
8
  // src/stryker.ts
9
9
  var WEAKENING_OPTIONS = ["ignoreStatic", "ignorers"];
10
- var DEFAULT_TIMEOUT_MS = 3e4;
10
+ var DEFAULT_MAX_TEST_RUNNER_REUSE = 50;
11
11
  function createStrykerGatesConfig(options = {}) {
12
12
  const { rootDir: rootDirOption, ...overrides } = options;
13
13
  for (const option of WEAKENING_OPTIONS) {
@@ -32,7 +32,7 @@ function createStrykerGatesConfig(options = {}) {
32
32
  incremental: true,
33
33
  incrementalFile: "reports/stryker-incremental.json",
34
34
  tempDirName: ".stryker-tmp",
35
- timeoutMS: DEFAULT_TIMEOUT_MS,
35
+ maxTestRunnerReuse: DEFAULT_MAX_TEST_RUNNER_REUSE,
36
36
  clearTextReporter: { logTests: false, reportTests: false },
37
37
  ...overrides,
38
38
  mutate: manifest.gates.map((gate) => gate.path),
@@ -42,6 +42,6 @@ function createStrykerGatesConfig(options = {}) {
42
42
  };
43
43
  }
44
44
  export {
45
- DEFAULT_TIMEOUT_MS,
45
+ DEFAULT_MAX_TEST_RUNNER_REUSE,
46
46
  createStrykerGatesConfig
47
47
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t09tanaka/test-gates",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
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",