oxmutate 2.2.0 → 2.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/binding/oxmutate.darwin-arm64.node +0 -0
- package/binding/oxmutate.darwin-x64.node +0 -0
- package/binding/oxmutate.linux-arm64-gnu.node +0 -0
- package/binding/oxmutate.linux-x64-gnu.node +0 -0
- package/binding/oxmutate.linux-x64-musl.node +0 -0
- package/binding/oxmutate.win32-x64-msvc.node +0 -0
- package/dist/child.js +4 -0
- package/dist/cli.js +6 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +7 -0
- package/dist/coordinator.d.ts +12 -0
- package/dist/coordinator.js +58 -5
- package/dist/native.d.ts +2 -0
- package/dist/reports.js +9 -1
- package/dist/runner.js +1 -1
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/child.js
CHANGED
|
@@ -75,6 +75,10 @@ async function runIsolated(unit) {
|
|
|
75
75
|
}
|
|
76
76
|
async function runUnit(unit) {
|
|
77
77
|
env['OXMUTATE_MODE'] = unit.mode;
|
|
78
|
+
if (unit.trackReads)
|
|
79
|
+
env['__OXMUTATE_TRACK_READS__'] = '1';
|
|
80
|
+
else
|
|
81
|
+
delete env['__OXMUTATE_TRACK_READS__'];
|
|
78
82
|
env['OXMUTATE_RESULTS'] = unit.results;
|
|
79
83
|
env['OXMUTATE_PROGRESS'] = unit.progress;
|
|
80
84
|
if (unit.plan)
|
package/dist/cli.js
CHANGED
|
@@ -62,6 +62,10 @@ 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']);
|
|
67
|
+
if (o['reportStaticReaders'] === true)
|
|
68
|
+
overrides.staticReaders = 'report';
|
|
65
69
|
if (o['incremental'] === true)
|
|
66
70
|
overrides.incremental = true;
|
|
67
71
|
if (typeof o['flakyReruns'] === 'number')
|
|
@@ -93,6 +97,8 @@ const runCommand = defineCommand({
|
|
|
93
97
|
strykerCompat: { type: 'boolean', description: 'reproduce Stryker 10.0.0 skips (no mutants inside `as` casts or enums)' },
|
|
94
98
|
ignoreStatic: { type: 'boolean', description: 'skip static mutants (module-level code): faster, and the score leaves them out' },
|
|
95
99
|
staticGroup: { type: 'number', description: 'arm this many static mutants together in a file that is not their first (default 1; a heuristic, audited)' },
|
|
100
|
+
staticBudget: { type: 'number', description: 'the most the static phase may take, ms; static mutants still undecided then are left out of the score' },
|
|
101
|
+
reportStaticReaders: { type: 'boolean', description: 'one more dry run that reports how many tests static mutants would need if each ran only where its value is read; changes no verdict' },
|
|
96
102
|
incremental: { type: 'boolean', description: 'reuse verdicts whose source and covering tests did not change' },
|
|
97
103
|
flakyReruns: { type: 'number', description: 're-runs for verdicts with nondeterministic tests (default 2; 0 disables)' },
|
|
98
104
|
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,18 @@ 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;
|
|
36
|
+
/**
|
|
37
|
+
* `static.readers: 'report'`: one more dry run, with every module-level literal reporting which
|
|
38
|
+
* tests read each of its properties, and a summary line of how many tests the static phase would
|
|
39
|
+
* run if each static mutant ran only where its value is read (roadmap 2.4). Changes no verdict.
|
|
40
|
+
*/
|
|
41
|
+
staticReaders: 'off' | 'report';
|
|
28
42
|
strykerCompat: boolean;
|
|
29
43
|
reporters: string[];
|
|
30
44
|
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,8 @@ 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),
|
|
114
|
+
staticReaders: raw['static']?.readers === 'report' ? 'report' : 'off',
|
|
108
115
|
strykerCompat: raw['strykerCompat'] === true,
|
|
109
116
|
reporters: raw['reporters'] ?? ['clear-text', 'json'],
|
|
110
117
|
jsonReport: path.resolve(root, json.fileName ?? 'reports/mutation/mutation.json'),
|
package/dist/coordinator.d.ts
CHANGED
|
@@ -76,6 +76,18 @@ export interface RunStats {
|
|
|
76
76
|
pollutionRestarts: number;
|
|
77
77
|
/** Units after which the project on disk had changed (diskguard.ts): put back, and their verdicts and those of the units running beside them re-run. */
|
|
78
78
|
diskWrites: number;
|
|
79
|
+
/**
|
|
80
|
+
* `static.readers: 'report'` (roadmap 2.4): over the static mutants, the tests of every file that
|
|
81
|
+
* loads each one's module (what the static phase runs), and the tests of only the files that read
|
|
82
|
+
* its value. A mutant whose value is untracked, or read while a module loaded, counts its loaders.
|
|
83
|
+
*/
|
|
84
|
+
staticReaders?: {
|
|
85
|
+
mutants: number;
|
|
86
|
+
loaderTests: number;
|
|
87
|
+
readerTests: number;
|
|
88
|
+
fellBack: number;
|
|
89
|
+
dryRunMs: number;
|
|
90
|
+
} | undefined;
|
|
79
91
|
/** The first few paths those writes touched, so a suite's own scratch in a tracked folder can be told from a mutant's write. */
|
|
80
92
|
diskWritePaths: string[];
|
|
81
93
|
/** Survivors of the warm run that a whole-file re-run killed (cross-test effects). */
|
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,
|
|
@@ -120,6 +121,7 @@ export async function run(options) {
|
|
|
120
121
|
throw new InstrumentFault(`cannot parse ${String(parseErrors.length)} file(s): ${parseErrors.slice(0, 3).join('; ')}`);
|
|
121
122
|
const changed = options.since ? await changedLines(root, options.since) : undefined;
|
|
122
123
|
const mutants = [];
|
|
124
|
+
const trackedIds = new Set();
|
|
123
125
|
const codeMap = {};
|
|
124
126
|
const directiveErrors = [];
|
|
125
127
|
for (const f of instrumented) {
|
|
@@ -138,8 +140,11 @@ export async function run(options) {
|
|
|
138
140
|
const off = offs.find((d) => d.line === m.start.line && (d.mutators === 'all' || d.mutators.includes(m.mutatorName)));
|
|
139
141
|
const inRange = ranged.reasonFor(m.start.line, m.mutatorName);
|
|
140
142
|
const excluded = options.excludedMutations.includes(m.mutatorName);
|
|
143
|
+
if (m.tracked)
|
|
144
|
+
trackedIds.add(m.id);
|
|
145
|
+
const { tracked: _tracked, ...plain } = m;
|
|
141
146
|
mutants.push({
|
|
142
|
-
...
|
|
147
|
+
...plain,
|
|
143
148
|
file: f.path,
|
|
144
149
|
coveredBy: [],
|
|
145
150
|
status: off || inRange !== undefined || excluded || m.ignoredReason ? 'Ignored' : 'NoCoverage',
|
|
@@ -183,8 +188,8 @@ export async function run(options) {
|
|
|
183
188
|
env[SPEC_TRACES_ENV] = JSON.stringify(traceDirs);
|
|
184
189
|
const dryPool = createPool({ root, vitestConfig: options.vitestConfig, workers: options.concurrency, instrumentedMap: mapFile, nodeArgs: options.testRunnerNodeArgs }, 1, work, 'dry');
|
|
185
190
|
pools.push(dryPool);
|
|
186
|
-
const dryRun = async () => {
|
|
187
|
-
const unit = { mode: 'dry', files: [], results: fileFor('dry', 'jsonl'), progress: fileFor('dry', 'progress') };
|
|
191
|
+
const dryRun = async (trackReads = false) => {
|
|
192
|
+
const unit = { mode: 'dry', files: [], results: fileFor('dry', 'jsonl'), progress: fileFor('dry', 'progress'), ...(trackReads && { trackReads: true }) };
|
|
188
193
|
let failure;
|
|
189
194
|
let overBudget = false;
|
|
190
195
|
await dryPool.drain([unit], () => options.dryRunTimeoutMinutes * MS_PER_MINUTE, (_, o) => {
|
|
@@ -201,10 +206,13 @@ export async function run(options) {
|
|
|
201
206
|
throw new InstrumentFault(`the dry run did not complete: ${failure.stderr.slice(-1500)}`);
|
|
202
207
|
const perTest = {};
|
|
203
208
|
const stat = {};
|
|
209
|
+
const reads = { static: {}, perTest: {} };
|
|
204
210
|
for (const r of recs) {
|
|
205
211
|
Object.assign(perTest, r.coverage?.perTest ?? {});
|
|
206
212
|
for (const [k, v] of Object.entries(r.coverage?.static ?? {}))
|
|
207
213
|
stat[k] = (stat[k] ?? 0) + v;
|
|
214
|
+
Object.assign(reads.perTest, r.reads?.perTest ?? {});
|
|
215
|
+
Object.assign(reads.static, r.reads?.static ?? {});
|
|
208
216
|
}
|
|
209
217
|
const fileErrors = recs.flatMap((r) => r.fileErrors ?? []);
|
|
210
218
|
if (fileErrors.length) {
|
|
@@ -213,12 +221,17 @@ export async function run(options) {
|
|
|
213
221
|
const unhandled = recs.reduce((n, r) => n + (r.unhandled ?? 0), 0);
|
|
214
222
|
if (unhandled > 0)
|
|
215
223
|
throw new InstrumentFault(`the suite raised ${String(unhandled)} unhandled error(s) with no mutant active, so no verdict can be trusted. fix: make \`vitest run\` pass with no "Unhandled Errors" first`);
|
|
216
|
-
return { tests: recs.flatMap((r) => r.tests ?? []), perTest, static: stat, runOnce: recs.flatMap((r) => r.runOnce ?? []), modules: Object.assign({}, ...recs.map((r) => r.modules ?? {})) };
|
|
224
|
+
return { tests: recs.flatMap((r) => r.tests ?? []), perTest, static: stat, runOnce: recs.flatMap((r) => r.runOnce ?? []), modules: Object.assign({}, ...recs.map((r) => r.modules ?? {})), reads };
|
|
217
225
|
};
|
|
218
226
|
progress.phase('dry run');
|
|
219
227
|
const d0 = performance.now();
|
|
220
228
|
const dry = await dryRun();
|
|
221
229
|
stats.dryRunMs = performance.now() - d0;
|
|
230
|
+
const r0 = performance.now();
|
|
231
|
+
if (options.staticReaders === 'report' && trackedIds.size > 0)
|
|
232
|
+
progress.phase('dry run with reads tracked (static.readers)');
|
|
233
|
+
const tracking = options.staticReaders === 'report' && trackedIds.size > 0 ? await dryRun(true) : undefined;
|
|
234
|
+
const readersDryRunMs = performance.now() - r0;
|
|
222
235
|
const failing = dry.tests.filter((t) => t.state === 'fail');
|
|
223
236
|
if (failing.length)
|
|
224
237
|
throw new InstrumentFault(`${String(failing.length)} test(s) fail with no mutant active, so no verdict can be trusted: ${failing.slice(0, 5).map((t) => t.name).join('; ')}. fix: make the suite pass first`);
|
|
@@ -575,6 +588,36 @@ export async function run(options) {
|
|
|
575
588
|
const covering = byCost([...new Set(tests)]);
|
|
576
589
|
return [...covering, ...byAffinity(m.file, [...loaders].filter((f) => !covering.includes(f)))];
|
|
577
590
|
};
|
|
591
|
+
const readersOf = (items) => {
|
|
592
|
+
const testsIn = new Map();
|
|
593
|
+
for (const t of dry.tests)
|
|
594
|
+
testsIn.set(t.file, (testsIn.get(t.file) ?? 0) + 1);
|
|
595
|
+
const sum = (files) => [...files].reduce((n, f) => n + (testsIn.get(f) ?? 0), 0);
|
|
596
|
+
const reads = tracking?.reads ?? { static: {}, perTest: {} };
|
|
597
|
+
const broken = tracking?.tests.some((t) => t.state === 'fail') ?? true;
|
|
598
|
+
const readerFiles = new Map();
|
|
599
|
+
for (const [key, ids] of Object.entries(reads.perTest)) {
|
|
600
|
+
const file = key.startsWith(FILE_KEY) ? key.slice(FILE_KEY.length) : testInfo.get(key)?.file;
|
|
601
|
+
if (!file)
|
|
602
|
+
continue;
|
|
603
|
+
for (const id of Object.keys(ids))
|
|
604
|
+
(readerFiles.get(id) ?? readerFiles.set(id, new Set()).get(id))?.add(file);
|
|
605
|
+
}
|
|
606
|
+
let loaderTests = 0;
|
|
607
|
+
let readerTests = 0;
|
|
608
|
+
let fellBack = 0;
|
|
609
|
+
for (const m of items) {
|
|
610
|
+
const loaders = filesFor(m);
|
|
611
|
+
loaderTests += sum(loaders);
|
|
612
|
+
if (broken || !trackedIds.has(m.id) || m.id in reads.static) {
|
|
613
|
+
fellBack++;
|
|
614
|
+
readerTests += sum(loaders);
|
|
615
|
+
}
|
|
616
|
+
else
|
|
617
|
+
readerTests += sum(loaders.filter((f) => readerFiles.get(m.id)?.has(f)));
|
|
618
|
+
}
|
|
619
|
+
return { mutants: items.length, loaderTests, readerTests, fellBack };
|
|
620
|
+
};
|
|
578
621
|
const testsRun = (id) => {
|
|
579
622
|
const m = byId.get(id);
|
|
580
623
|
const fs = m ? filesFor(m) : allTestFiles;
|
|
@@ -649,7 +692,11 @@ export async function run(options) {
|
|
|
649
692
|
back.add(id);
|
|
650
693
|
return [];
|
|
651
694
|
};
|
|
695
|
+
const deadline = options.staticBudgetMs === undefined ? Infinity : performance.now() + options.staticBudgetMs;
|
|
696
|
+
const spent = () => performance.now() >= deadline;
|
|
652
697
|
const keepUndecided = (unit) => {
|
|
698
|
+
if (spent())
|
|
699
|
+
return false;
|
|
653
700
|
unit.staticIds = (unit.staticIds ?? []).filter((id) => !killed.has(id) && !back.has(id));
|
|
654
701
|
if (unit.staticGroups)
|
|
655
702
|
unit.staticGroups = unit.staticGroups.map((g) => g.filter((id) => !killed.has(id) && !back.has(id))).filter((g) => g.length > 0);
|
|
@@ -671,6 +718,10 @@ export async function run(options) {
|
|
|
671
718
|
m.status = 'Survived';
|
|
672
719
|
m.testsCompleted = ran.get(m.id) ?? 0;
|
|
673
720
|
}
|
|
721
|
+
else if (spent()) {
|
|
722
|
+
m.status = 'Ignored';
|
|
723
|
+
m.statusReason = STATIC_BUDGET_REASON;
|
|
724
|
+
}
|
|
674
725
|
else
|
|
675
726
|
undecided.push(m);
|
|
676
727
|
}
|
|
@@ -718,6 +769,8 @@ export async function run(options) {
|
|
|
718
769
|
verdicts.set(m.id, { status: 'RuntimeError' });
|
|
719
770
|
return verdicts;
|
|
720
771
|
};
|
|
772
|
+
if (tracking)
|
|
773
|
+
stats.staticReaders = { ...readersOf(staticMutants), dryRunMs: readersDryRunMs };
|
|
721
774
|
const s0 = performance.now();
|
|
722
775
|
const staticBack = env['OXMUTATE_ISOLATE_STATIC'] ? staticMutants : await decideStaticLive(staticMutants);
|
|
723
776
|
stats.staticMs = performance.now() - s0;
|
package/dist/native.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface NativeMutant {
|
|
|
10
10
|
end: Location;
|
|
11
11
|
/** Set when an ignore rule covers the mutant: reported as Ignored, never placed in the code. */
|
|
12
12
|
ignoredReason?: string;
|
|
13
|
+
/** Reads of the property holding it are reported by a tracking dry run (`oxmTrack`, roadmap 2.4). */
|
|
14
|
+
tracked?: boolean;
|
|
13
15
|
}
|
|
14
16
|
export interface InstrumentedFile {
|
|
15
17
|
path: string;
|
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
|
}
|
|
@@ -137,6 +140,11 @@ function whereItWent(r) {
|
|
|
137
140
|
if (s.warmUnitMs > 0)
|
|
138
141
|
lines.push(` warm servers ran ${(s.warmUnitMs / MS_PER_S).toFixed(1)} s, ${(s.warmArmedMs / MS_PER_S).toFixed(1)} s of it inside test runs`);
|
|
139
142
|
lines.push(...staticLine(r));
|
|
143
|
+
const sr = s.staticReaders;
|
|
144
|
+
if (sr && sr.mutants > 0) {
|
|
145
|
+
const ratio = sr.readerTests > 0 ? `, ${(sr.loaderTests / sr.readerTests).toFixed(1)}x fewer` : '';
|
|
146
|
+
lines.push(` static readers: ${String(sr.mutants)} static mutants would run ${String(sr.readerTests)} tests where their value is read, against ${String(sr.loaderTests)} in every file that loads them${ratio}; ${String(sr.fellBack)} kept every loader (report only, ${(sr.dryRunMs / MS_PER_S).toFixed(1)} s)`);
|
|
147
|
+
}
|
|
140
148
|
const c = s.isolatedByCause;
|
|
141
149
|
const causes = [`static ${String(c.static)}`, `stopped ${String(c.stopped)}`, `unfinished ${String(c.unfinished)}`, `re-run ${String(c.rerun)}`, `survivors confirmed ${String(c.confirm)}`, `flaky ${String(c.flaky)}`];
|
|
142
150
|
if (s.isolatedRuns > 0)
|
package/dist/runner.js
CHANGED
|
@@ -655,7 +655,7 @@ export default class OxmutateRunner extends Base {
|
|
|
655
655
|
if (result?.state === 'fail' && !f.tasks?.length)
|
|
656
656
|
fileErrors.push({ file: filepath, error: (result.errors?.[0]?.message ?? 'failed to collect').slice(0, FILE_ERROR_CHARS) });
|
|
657
657
|
}
|
|
658
|
-
emit({ type: 'dry', tests, coverage: ns.mutantCoverage, fileErrors, runOnce: [...runOnce], modules: Object.fromEntries(dryModules), unhandled });
|
|
658
|
+
emit({ type: 'dry', tests, coverage: ns.mutantCoverage, reads: ns.readCoverage, fileErrors, runOnce: [...runOnce], modules: Object.fromEntries(dryModules), unhandled });
|
|
659
659
|
}
|
|
660
660
|
else if (mode === 'isolated') {
|
|
661
661
|
const failed = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxmutate",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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>",
|