oxmutate 2.2.0 → 2.3.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/binding/oxmutate.win32-x64-msvc.node +0 -0
- package/dist/cli.js +3 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +6 -0
- package/dist/coordinator.js +10 -1
- package/dist/reports.js +4 -1
- package/package.json +1 -1
|
Binary file
|
package/dist/cli.js
CHANGED
|
@@ -62,6 +62,8 @@ function overridesFrom(o, root) {
|
|
|
62
62
|
overrides.ignoreStatic = true;
|
|
63
63
|
if (typeof o['staticGroup'] === 'number')
|
|
64
64
|
overrides.staticGroup = Math.max(1, Math.floor(o['staticGroup']));
|
|
65
|
+
if (typeof o['staticBudget'] === 'number' && o['staticBudget'] >= 1)
|
|
66
|
+
overrides.staticBudgetMs = Math.floor(o['staticBudget']);
|
|
65
67
|
if (o['incremental'] === true)
|
|
66
68
|
overrides.incremental = true;
|
|
67
69
|
if (typeof o['flakyReruns'] === 'number')
|
|
@@ -93,6 +95,7 @@ const runCommand = defineCommand({
|
|
|
93
95
|
strykerCompat: { type: 'boolean', description: 'reproduce Stryker 10.0.0 skips (no mutants inside `as` casts or enums)' },
|
|
94
96
|
ignoreStatic: { type: 'boolean', description: 'skip static mutants (module-level code): faster, and the score leaves them out' },
|
|
95
97
|
staticGroup: { type: 'number', description: 'arm this many static mutants together in a file that is not their first (default 1; a heuristic, audited)' },
|
|
98
|
+
staticBudget: { type: 'number', description: 'the most the static phase may take, ms; static mutants still undecided then are left out of the score' },
|
|
96
99
|
incremental: { type: 'boolean', description: 'reuse verdicts whose source and covering tests did not change' },
|
|
97
100
|
flakyReruns: { type: 'number', description: 're-runs for verdicts with nondeterministic tests (default 2; 0 disables)' },
|
|
98
101
|
reporter: { type: 'string', description: 'comma-separated: clear-text, json, sarif, html (default clear-text,json)' },
|
package/dist/config.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface Thresholds {
|
|
|
6
6
|
/** Fail the run (exit 1) below this score. `null`: never fail on score. */
|
|
7
7
|
break: number | null;
|
|
8
8
|
}
|
|
9
|
+
/** Why a static mutant is Ignored when `static.budgetMs` ran out before it was decided; the summary counts them by it. */
|
|
10
|
+
export declare const STATIC_BUDGET_REASON = "static mutant (static budget spent)";
|
|
9
11
|
/** Why a static mutant is Ignored under `ignoreStatic`; the summary counts them by it. */
|
|
10
12
|
export declare const IGNORE_STATIC_REASON = "static mutant (ignoreStatic)";
|
|
11
13
|
export interface Options {
|
|
@@ -25,6 +27,12 @@ export interface Options {
|
|
|
25
27
|
* heuristic: two mutants can hide each other, so one in twenty group survivors is run again alone.
|
|
26
28
|
*/
|
|
27
29
|
staticGroup: number;
|
|
30
|
+
/**
|
|
31
|
+
* The most the static phase may take, in milliseconds (`static.budgetMs`). No new static unit
|
|
32
|
+
* starts once it is spent; a static mutant still undecided then is Ignored, with a reason, and
|
|
33
|
+
* left out of the score. A kill found before that stands. Unset: no limit.
|
|
34
|
+
*/
|
|
35
|
+
staticBudgetMs?: number | undefined;
|
|
28
36
|
strykerCompat: boolean;
|
|
29
37
|
reporters: string[];
|
|
30
38
|
jsonReport: string;
|
package/dist/config.js
CHANGED
|
@@ -7,6 +7,11 @@ function staticGroupOf(group) {
|
|
|
7
7
|
const asked = Math.floor(Number(group));
|
|
8
8
|
return asked >= 2 ? asked : 1;
|
|
9
9
|
}
|
|
10
|
+
function staticBudgetOf(ms) {
|
|
11
|
+
const asked = Math.floor(Number(ms));
|
|
12
|
+
return asked >= 1 ? asked : undefined;
|
|
13
|
+
}
|
|
14
|
+
export const STATIC_BUDGET_REASON = 'static mutant (static budget spent)';
|
|
10
15
|
export const IGNORE_STATIC_REASON = 'static mutant (ignoreStatic)';
|
|
11
16
|
const NOT_NEEDED = {
|
|
12
17
|
inPlace: 'oxmutate never copies or rewrites the project; instrumented sources are served from memory',
|
|
@@ -105,6 +110,7 @@ export async function loadOptions(root, overrides = {}, configFile) {
|
|
|
105
110
|
excludedMutations: mutator.excludedMutations ?? [],
|
|
106
111
|
ignoreStatic: raw['ignoreStatic'] === true,
|
|
107
112
|
staticGroup: staticGroupOf(raw['static']?.group),
|
|
113
|
+
staticBudgetMs: staticBudgetOf(raw['static']?.budgetMs),
|
|
108
114
|
strykerCompat: raw['strykerCompat'] === true,
|
|
109
115
|
reporters: raw['reporters'] ?? ['clear-text', 'json'],
|
|
110
116
|
jsonReport: path.resolve(root, json.fileName ?? 'reports/mutation/mutation.json'),
|
package/dist/coordinator.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { ambientRuntime, run as spawn } from 'bellpull';
|
|
6
6
|
import { checkSurvivors } from './checker.js';
|
|
7
7
|
import { runCommandMutants } from './command-runner.js';
|
|
8
|
-
import { IGNORE_STATIC_REASON, disables } from './config.js';
|
|
8
|
+
import { IGNORE_STATIC_REASON, STATIC_BUDGET_REASON, disables } from './config.js';
|
|
9
9
|
import { rangeDisables } from './directives.js';
|
|
10
10
|
import { guardDisk, isClean } from './diskguard.js';
|
|
11
11
|
import { globToRegExp, matchFiles } from './files.js';
|
|
@@ -43,6 +43,7 @@ const verdictOptions = (o) => ({
|
|
|
43
43
|
excludedMutations: [...o.excludedMutations].sort(),
|
|
44
44
|
ignoreStatic: o.ignoreStatic,
|
|
45
45
|
staticGroup: o.staticGroup,
|
|
46
|
+
staticBudgetMs: o.staticBudgetMs,
|
|
46
47
|
strykerCompat: o.strykerCompat,
|
|
47
48
|
testOverrides: o.testOverrides,
|
|
48
49
|
vitestConfig: o.vitestConfig && existsSync(o.vitestConfig) ? sha(readFileSync(o.vitestConfig, 'utf8')) : null,
|
|
@@ -649,7 +650,11 @@ export async function run(options) {
|
|
|
649
650
|
back.add(id);
|
|
650
651
|
return [];
|
|
651
652
|
};
|
|
653
|
+
const deadline = options.staticBudgetMs === undefined ? Infinity : performance.now() + options.staticBudgetMs;
|
|
654
|
+
const spent = () => performance.now() >= deadline;
|
|
652
655
|
const keepUndecided = (unit) => {
|
|
656
|
+
if (spent())
|
|
657
|
+
return false;
|
|
653
658
|
unit.staticIds = (unit.staticIds ?? []).filter((id) => !killed.has(id) && !back.has(id));
|
|
654
659
|
if (unit.staticGroups)
|
|
655
660
|
unit.staticGroups = unit.staticGroups.map((g) => g.filter((id) => !killed.has(id) && !back.has(id))).filter((g) => g.length > 0);
|
|
@@ -671,6 +676,10 @@ export async function run(options) {
|
|
|
671
676
|
m.status = 'Survived';
|
|
672
677
|
m.testsCompleted = ran.get(m.id) ?? 0;
|
|
673
678
|
}
|
|
679
|
+
else if (spent()) {
|
|
680
|
+
m.status = 'Ignored';
|
|
681
|
+
m.statusReason = STATIC_BUDGET_REASON;
|
|
682
|
+
}
|
|
674
683
|
else
|
|
675
684
|
undecided.push(m);
|
|
676
685
|
}
|
package/dist/reports.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { IGNORE_STATIC_REASON } from './config.js';
|
|
4
|
+
import { IGNORE_STATIC_REASON, STATIC_BUDGET_REASON } from './config.js';
|
|
5
5
|
export const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
6
6
|
const REPLACEMENT_WIDTH = 60;
|
|
7
7
|
const ELLIPSIS = '...';
|
|
@@ -128,6 +128,9 @@ function staticLine(r) {
|
|
|
128
128
|
if (ignored > 0)
|
|
129
129
|
return [` static mutants: ${String(ignored)} ignored (ignoreStatic), not scored`];
|
|
130
130
|
const share = r.stats.wallMs > 0 ? r.stats.staticMs / r.stats.wallMs : 0;
|
|
131
|
+
const unspent = statics.filter((m) => m.statusReason === STATIC_BUDGET_REASON).length;
|
|
132
|
+
if (unspent > 0)
|
|
133
|
+
return [` static mutants: ${String(statics.length - unspent)} decided in ${(r.stats.staticMs / MS_PER_S).toFixed(1)} s, ${String(unspent)} left undecided when the static budget ran out, not scored`];
|
|
131
134
|
const hint = share >= STATIC_SHARE_WORTH_SAYING ? '; --ignore-static skips them, and the score then leaves them out' : '';
|
|
132
135
|
return [` static mutants: ${String(statics.length)} decided in ${(r.stats.staticMs / MS_PER_S).toFixed(1)} s, ${(share * PERCENT).toFixed(0)}% of the run${hint}`];
|
|
133
136
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxmutate",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Mutation testing for JavaScript and TypeScript on the oxc engine, run warm inside the project's own Vitest, with CI lanes, SARIF and an agent surface. No dependency outside the burgee family. A Stryker migration path.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ofri Peretz <ofriperetzdev@gmail.com>",
|