@mutineerjs/mutineer 0.2.4 → 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/README.md +48 -42
- package/dist/bin/mutineer.js +0 -0
- package/dist/mutators/__tests__/operator.spec.js +169 -0
- package/dist/mutators/__tests__/registry.spec.js +6 -0
- package/dist/mutators/__tests__/return-value.spec.js +239 -0
- package/dist/mutators/__tests__/utils.spec.js +68 -1
- package/dist/mutators/operator.d.ts +25 -0
- package/dist/mutators/operator.js +50 -0
- package/dist/mutators/registry.d.ts +6 -28
- package/dist/mutators/registry.js +14 -66
- package/dist/mutators/return-value.d.ts +39 -0
- package/dist/mutators/return-value.js +104 -0
- package/dist/mutators/utils.d.ts +21 -0
- package/dist/mutators/utils.js +44 -27
- package/dist/runner/__tests__/pool-executor.spec.js +95 -4
- package/dist/runner/__tests__/variants.spec.js +3 -1
- package/dist/runner/discover.js +2 -1
- package/dist/runner/jest/__tests__/adapter.spec.js +1 -1
- package/dist/runner/jest/__tests__/pool.spec.js +5 -6
- package/dist/runner/jest/__tests__/worker-runtime.spec.js +2 -2
- package/dist/runner/jest/adapter.js +1 -1
- package/dist/runner/jest/pool.d.ts +1 -1
- package/dist/runner/jest/pool.js +6 -6
- package/dist/runner/jest/worker.mjs +1 -1
- package/dist/runner/pool-executor.js +21 -1
- package/dist/runner/shared/__tests__/redirect-state.spec.js +3 -3
- package/dist/runner/shared/index.d.ts +1 -1
- package/dist/runner/shared/index.js +1 -1
- package/dist/runner/shared/redirect-state.d.ts +2 -2
- package/dist/runner/shared/redirect-state.js +4 -4
- package/dist/runner/types.d.ts +1 -1
- package/dist/runner/vitest/__tests__/adapter.spec.js +1 -1
- package/dist/runner/vitest/__tests__/pool.spec.js +1 -1
- package/dist/runner/vitest/__tests__/redirect-loader.spec.js +83 -1
- package/dist/runner/vitest/__tests__/worker-runtime.spec.js +84 -0
- package/dist/runner/vitest/adapter.js +1 -1
- package/dist/runner/vitest/index.d.ts +0 -1
- package/dist/runner/vitest/index.js +0 -1
- package/dist/runner/vitest/pool.d.ts +1 -1
- package/dist/runner/vitest/pool.js +7 -7
- package/dist/runner/vitest/redirect-loader.d.ts +1 -1
- package/dist/runner/vitest/redirect-loader.js +1 -1
- package/dist/runner/vitest/worker-runtime.js +3 -3
- package/dist/runner/vitest/worker.mjs +1 -1
- package/dist/types/mutant.d.ts +2 -0
- package/dist/utils/__tests__/coverage.spec.js +167 -0
- package/dist/utils/__tests__/progress.spec.js +96 -0
- package/dist/utils/__tests__/summary.spec.js +28 -0
- package/dist/utils/summary.js +7 -1
- package/package.json +71 -22
- package/dist/admin/assets/index-B7nXq-e7.js +0 -32
- package/dist/admin/assets/index-B7nXq-e7.js.map +0 -1
- package/dist/admin/assets/index-BDQLkBUE.js +0 -32
- package/dist/admin/assets/index-BDQLkBUE.js.map +0 -1
- package/dist/admin/assets/index-DVkP-Tc7.css +0 -1
- package/dist/admin/index.html +0 -13
- package/dist/admin/server/admin.d.ts +0 -6
- package/dist/admin/server/admin.js +0 -234
- package/dist/bin/mutate-vitest.d.ts +0 -2
- package/dist/bin/mutate-vitest.js +0 -90
- package/dist/plugin/viteMutate.d.ts +0 -15
- package/dist/plugin/viteMutate.js +0 -52
- package/dist/plugin/vitest.setup.d.ts +0 -47
- package/dist/plugin/vitest.setup.js +0 -118
- package/dist/plugin/withVitest.d.ts +0 -13
- package/dist/plugin/withVitest.js +0 -30
- package/dist/runner/__tests__/orchestrator.spec.js +0 -55
- package/dist/runner/adapters/__tests__/jest.spec.js +0 -88
- package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.d.ts +0 -1
- package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.js +0 -59
- package/dist/runner/adapters/__tests__/vitest.spec.d.ts +0 -1
- package/dist/runner/adapters/__tests__/vitest.spec.js +0 -118
- package/dist/runner/adapters/index.d.ts +0 -10
- package/dist/runner/adapters/index.js +0 -9
- package/dist/runner/adapters/jest/__tests__/index.spec.d.ts +0 -1
- package/dist/runner/adapters/jest/__tests__/index.spec.js +0 -88
- package/dist/runner/adapters/jest/index.d.ts +0 -24
- package/dist/runner/adapters/jest/index.js +0 -216
- package/dist/runner/adapters/jest/worker-runtime.d.ts +0 -37
- package/dist/runner/adapters/jest/worker-runtime.js +0 -171
- package/dist/runner/adapters/jest-worker-runtime.d.ts +0 -37
- package/dist/runner/adapters/jest-worker-runtime.js +0 -171
- package/dist/runner/adapters/jest.d.ts +0 -24
- package/dist/runner/adapters/jest.js +0 -216
- package/dist/runner/adapters/types.d.ts +0 -89
- package/dist/runner/adapters/types.js +0 -8
- package/dist/runner/adapters/vitest/__tests__/index.spec.d.ts +0 -1
- package/dist/runner/adapters/vitest/__tests__/index.spec.js +0 -118
- package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.d.ts +0 -1
- package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.js +0 -59
- package/dist/runner/adapters/vitest/index.d.ts +0 -33
- package/dist/runner/adapters/vitest/index.js +0 -267
- package/dist/runner/adapters/vitest/worker-runtime.d.ts +0 -25
- package/dist/runner/adapters/vitest/worker-runtime.js +0 -118
- package/dist/runner/adapters/vitest-worker-runtime.d.ts +0 -25
- package/dist/runner/adapters/vitest-worker-runtime.js +0 -118
- package/dist/runner/adapters/vitest.d.ts +0 -33
- package/dist/runner/adapters/vitest.js +0 -267
- package/dist/runner/pool/__tests__/index.spec.d.ts +0 -1
- package/dist/runner/pool/__tests__/index.spec.js +0 -83
- package/dist/runner/pool/__tests__/pool-plugin.spec.d.ts +0 -1
- package/dist/runner/pool/__tests__/pool-plugin.spec.js +0 -59
- package/dist/runner/pool/__tests__/pool-redirect-loader.spec.d.ts +0 -1
- package/dist/runner/pool/__tests__/pool-redirect-loader.spec.js +0 -78
- package/dist/runner/pool/index.d.ts +0 -8
- package/dist/runner/pool/index.js +0 -9
- package/dist/runner/pool/jest/pool.d.ts +0 -52
- package/dist/runner/pool/jest/pool.js +0 -309
- package/dist/runner/pool/jest/worker.d.mts +0 -1
- package/dist/runner/pool/jest/worker.mjs +0 -60
- package/dist/runner/pool/jest-pool.d.ts +0 -52
- package/dist/runner/pool/jest-pool.js +0 -309
- package/dist/runner/pool/jest-worker.d.mts +0 -1
- package/dist/runner/pool/jest-worker.mjs +0 -60
- package/dist/runner/pool/plugin.d.ts +0 -18
- package/dist/runner/pool/plugin.js +0 -60
- package/dist/runner/pool/pool-plugin.d.ts +0 -18
- package/dist/runner/pool/pool-plugin.js +0 -60
- package/dist/runner/pool/pool-redirect-loader.d.ts +0 -19
- package/dist/runner/pool/pool-redirect-loader.js +0 -116
- package/dist/runner/pool/pool-redirect-loader.mjs +0 -146
- package/dist/runner/pool/redirect-loader.d.ts +0 -19
- package/dist/runner/pool/redirect-loader.js +0 -116
- package/dist/runner/pool/vitest/pool.d.ts +0 -70
- package/dist/runner/pool/vitest/pool.js +0 -376
- package/dist/runner/pool/vitest/worker.d.mts +0 -15
- package/dist/runner/pool/vitest/worker.mjs +0 -96
- package/dist/runner/pool/vitest-worker.d.mts +0 -15
- package/dist/runner/pool/vitest-worker.mjs +0 -96
- package/dist/runner/shared-module-redirect.d.ts +0 -56
- package/dist/runner/shared-module-redirect.js +0 -84
- package/dist/types/api.d.ts +0 -20
- package/dist/types/api.js +0 -1
- /package/dist/{runner/__tests__/orchestrator.spec.d.ts → mutators/__tests__/operator.spec.d.ts} +0 -0
- /package/dist/{runner/adapters/__tests__/jest.spec.d.ts → mutators/__tests__/return-value.spec.d.ts} +0 -0
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vitest Worker Pool
|
|
3
|
-
*
|
|
4
|
-
* Manages a pool of persistent Vitest worker processes that can run
|
|
5
|
-
* multiple mutations without restarting, providing significant speedup
|
|
6
|
-
* over the per-spawn approach.
|
|
7
|
-
*
|
|
8
|
-
* Each worker:
|
|
9
|
-
* - Starts Vitest in watch mode via programmatic API
|
|
10
|
-
* - Receives mutations via stdin (JSON)
|
|
11
|
-
* - Uses dynamic redirect loader to swap module at runtime
|
|
12
|
-
* - Returns results via stdout (JSON)
|
|
13
|
-
*/
|
|
14
|
-
import { spawn } from 'node:child_process';
|
|
15
|
-
import * as path from 'node:path';
|
|
16
|
-
import * as readline from 'node:readline';
|
|
17
|
-
import * as fs from 'node:fs';
|
|
18
|
-
import { fileURLToPath } from 'node:url';
|
|
19
|
-
import { EventEmitter } from 'node:events';
|
|
20
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
-
class VitestWorker extends EventEmitter {
|
|
22
|
-
constructor(id, cwd, vitestConfig, debug = false) {
|
|
23
|
-
super();
|
|
24
|
-
this.cwd = cwd;
|
|
25
|
-
this.vitestConfig = vitestConfig;
|
|
26
|
-
this.debug = debug;
|
|
27
|
-
this.process = null;
|
|
28
|
-
this.rl = null;
|
|
29
|
-
this.pendingTask = null;
|
|
30
|
-
this.ready = false;
|
|
31
|
-
this.shuttingDown = false;
|
|
32
|
-
this.id = id;
|
|
33
|
-
}
|
|
34
|
-
async start() {
|
|
35
|
-
const workerJs = path.join(__dirname, 'worker.js');
|
|
36
|
-
const workerMts = path.join(__dirname, 'worker.mjs');
|
|
37
|
-
const workerTs = path.join(__dirname, 'worker.mts');
|
|
38
|
-
const workerScript = fs.existsSync(workerJs)
|
|
39
|
-
? workerJs
|
|
40
|
-
: fs.existsSync(workerMts)
|
|
41
|
-
? workerMts
|
|
42
|
-
: workerTs;
|
|
43
|
-
const loaderJs = path.join(__dirname, '..', 'redirect-loader.js');
|
|
44
|
-
const loaderMjs = path.join(__dirname, '..', 'redirect-loader.mjs');
|
|
45
|
-
const loaderTs = path.join(__dirname, '..', 'redirect-loader.ts');
|
|
46
|
-
const loaderScript = fs.existsSync(loaderJs)
|
|
47
|
-
? loaderJs
|
|
48
|
-
: fs.existsSync(loaderMjs)
|
|
49
|
-
? loaderMjs
|
|
50
|
-
: loaderTs;
|
|
51
|
-
const env = {
|
|
52
|
-
...process.env,
|
|
53
|
-
MUTINEER_WORKER_ID: this.id,
|
|
54
|
-
MUTINEER_CWD: this.cwd,
|
|
55
|
-
...(this.vitestConfig ? { MUTINEER_VITEST_CONFIG: this.vitestConfig } : {}),
|
|
56
|
-
...(this.debug ? { MUTINEER_DEBUG: '1' } : {}),
|
|
57
|
-
};
|
|
58
|
-
this.log('Starting worker process');
|
|
59
|
-
this.process = spawn(process.execPath, [
|
|
60
|
-
'--experimental-strip-types',
|
|
61
|
-
'--experimental-transform-types',
|
|
62
|
-
'--no-warnings',
|
|
63
|
-
'--import', loaderScript,
|
|
64
|
-
workerScript,
|
|
65
|
-
], {
|
|
66
|
-
cwd: this.cwd,
|
|
67
|
-
env,
|
|
68
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
69
|
-
});
|
|
70
|
-
// Handle stderr (debug/error output)
|
|
71
|
-
this.process.stderr?.on('data', (data) => {
|
|
72
|
-
if (this.debug) {
|
|
73
|
-
process.stderr.write(`[worker-${this.id}] ${data}`);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
// Set up line reader for stdout (JSON messages)
|
|
77
|
-
this.rl = readline.createInterface({
|
|
78
|
-
input: this.process.stdout,
|
|
79
|
-
terminal: false,
|
|
80
|
-
});
|
|
81
|
-
this.rl.on('line', (line) => this.handleMessage(line));
|
|
82
|
-
this.process.on('error', (err) => {
|
|
83
|
-
this.log(`Process error: ${err.message}`);
|
|
84
|
-
this.handleExit(1);
|
|
85
|
-
});
|
|
86
|
-
this.process.on('exit', (code) => {
|
|
87
|
-
this.log(`Process exited with code ${code}`);
|
|
88
|
-
this.handleExit(code ?? 1);
|
|
89
|
-
});
|
|
90
|
-
// Wait for ready signal
|
|
91
|
-
await new Promise((resolve, reject) => {
|
|
92
|
-
const timeoutMs = 120_000; // allow more time for Vitest init (coverage, large projects)
|
|
93
|
-
const timeout = setTimeout(() => {
|
|
94
|
-
reject(new Error(`Worker ${this.id} did not become ready in time (${timeoutMs}ms)`));
|
|
95
|
-
}, timeoutMs);
|
|
96
|
-
this.once('ready', () => {
|
|
97
|
-
clearTimeout(timeout);
|
|
98
|
-
resolve();
|
|
99
|
-
});
|
|
100
|
-
this.once('error', (err) => {
|
|
101
|
-
clearTimeout(timeout);
|
|
102
|
-
reject(err);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
handleMessage(line) {
|
|
107
|
-
const trimmed = line.trim();
|
|
108
|
-
if (!trimmed)
|
|
109
|
-
return;
|
|
110
|
-
// Only attempt to parse JSON lines; ignore any other stdout noise
|
|
111
|
-
if (!trimmed.startsWith('{')) {
|
|
112
|
-
if (this.debug)
|
|
113
|
-
this.log(`Non-JSON stdout: ${trimmed}`);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
let msg;
|
|
117
|
-
try {
|
|
118
|
-
msg = JSON.parse(trimmed);
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
this.log(`Invalid JSON from worker: ${line}`);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (msg.type === 'ready') {
|
|
125
|
-
this.ready = true;
|
|
126
|
-
this.emit('ready');
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (msg.type === 'result') {
|
|
130
|
-
if (this.pendingTask) {
|
|
131
|
-
const { resolve, timeoutHandle } = this.pendingTask;
|
|
132
|
-
if (timeoutHandle)
|
|
133
|
-
clearTimeout(timeoutHandle);
|
|
134
|
-
this.pendingTask = null;
|
|
135
|
-
resolve({
|
|
136
|
-
killed: msg.killed ?? true,
|
|
137
|
-
durationMs: msg.durationMs ?? 0,
|
|
138
|
-
error: msg.error,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
if (msg.type === 'shutdown') {
|
|
144
|
-
this.emit('shutdown');
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
handleExit(code) {
|
|
149
|
-
this.ready = false;
|
|
150
|
-
if (this.pendingTask && !this.shuttingDown) {
|
|
151
|
-
const { reject, timeoutHandle } = this.pendingTask;
|
|
152
|
-
if (timeoutHandle)
|
|
153
|
-
clearTimeout(timeoutHandle);
|
|
154
|
-
this.pendingTask = null;
|
|
155
|
-
reject(new Error(`Worker exited unexpectedly with code ${code}`));
|
|
156
|
-
}
|
|
157
|
-
this.emit('exit', code);
|
|
158
|
-
}
|
|
159
|
-
isReady() {
|
|
160
|
-
return this.ready && this.process !== null && !this.shuttingDown;
|
|
161
|
-
}
|
|
162
|
-
isBusy() {
|
|
163
|
-
return this.pendingTask !== null;
|
|
164
|
-
}
|
|
165
|
-
async run(mutant, tests, timeoutMs = 10_000) {
|
|
166
|
-
if (!this.isReady()) {
|
|
167
|
-
throw new Error(`Worker ${this.id} is not ready`);
|
|
168
|
-
}
|
|
169
|
-
if (this.isBusy()) {
|
|
170
|
-
throw new Error(`Worker ${this.id} is busy`);
|
|
171
|
-
}
|
|
172
|
-
return new Promise((resolve, reject) => {
|
|
173
|
-
const timeoutHandle = setTimeout(() => {
|
|
174
|
-
if (this.pendingTask) {
|
|
175
|
-
this.pendingTask = null;
|
|
176
|
-
// Kill and restart the worker on timeout
|
|
177
|
-
this.kill();
|
|
178
|
-
resolve({ killed: true, durationMs: timeoutMs, error: 'timeout' });
|
|
179
|
-
}
|
|
180
|
-
}, timeoutMs);
|
|
181
|
-
this.pendingTask = { resolve, reject, timeoutHandle };
|
|
182
|
-
const request = JSON.stringify({ type: 'run', mutant, tests });
|
|
183
|
-
this.process.stdin.write(request + '\n');
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
async shutdown() {
|
|
187
|
-
if (!this.process || this.shuttingDown)
|
|
188
|
-
return;
|
|
189
|
-
this.shuttingDown = true;
|
|
190
|
-
return new Promise((resolve) => {
|
|
191
|
-
const timeout = setTimeout(() => {
|
|
192
|
-
this.kill();
|
|
193
|
-
resolve();
|
|
194
|
-
}, 5000);
|
|
195
|
-
this.once('shutdown', () => {
|
|
196
|
-
clearTimeout(timeout);
|
|
197
|
-
resolve();
|
|
198
|
-
});
|
|
199
|
-
this.process.stdin.write(JSON.stringify({ type: 'shutdown' }) + '\n');
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
kill() {
|
|
203
|
-
if (this.process) {
|
|
204
|
-
try {
|
|
205
|
-
this.process.kill('SIGKILL');
|
|
206
|
-
}
|
|
207
|
-
catch {
|
|
208
|
-
// Ignore
|
|
209
|
-
}
|
|
210
|
-
this.process = null;
|
|
211
|
-
}
|
|
212
|
-
this.ready = false;
|
|
213
|
-
}
|
|
214
|
-
log(msg) {
|
|
215
|
-
if (this.debug) {
|
|
216
|
-
console.error(`[VitestWorker-${this.id}] ${msg}`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
export class VitestPool {
|
|
221
|
-
constructor(options) {
|
|
222
|
-
this.workers = [];
|
|
223
|
-
this.availableWorkers = [];
|
|
224
|
-
this.waitingTasks = [];
|
|
225
|
-
this.initialized = false;
|
|
226
|
-
this.shuttingDown = false;
|
|
227
|
-
this.options = {
|
|
228
|
-
cwd: options.cwd,
|
|
229
|
-
concurrency: options.concurrency,
|
|
230
|
-
vitestConfig: options.vitestConfig,
|
|
231
|
-
timeoutMs: options.timeoutMs ?? 10_000,
|
|
232
|
-
debug: options.debug ?? false,
|
|
233
|
-
createWorker: options.createWorker,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
async init() {
|
|
237
|
-
if (this.initialized)
|
|
238
|
-
return;
|
|
239
|
-
this.log(`Initializing pool with ${this.options.concurrency} workers`);
|
|
240
|
-
const startPromises = [];
|
|
241
|
-
for (let i = 0; i < this.options.concurrency; i++) {
|
|
242
|
-
const worker = this.options.createWorker?.(`w${i}`, {
|
|
243
|
-
cwd: this.options.cwd,
|
|
244
|
-
vitestConfig: this.options.vitestConfig,
|
|
245
|
-
debug: this.options.debug,
|
|
246
|
-
}) ??
|
|
247
|
-
new VitestWorker(`w${i}`, this.options.cwd, this.options.vitestConfig, this.options.debug);
|
|
248
|
-
worker.on('exit', () => {
|
|
249
|
-
if (!this.shuttingDown) {
|
|
250
|
-
this.handleWorkerExit(worker);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
this.workers.push(worker);
|
|
254
|
-
startPromises.push(worker.start().then(() => {
|
|
255
|
-
this.availableWorkers.push(worker);
|
|
256
|
-
this.log(`Worker ${worker.id} ready`);
|
|
257
|
-
}));
|
|
258
|
-
}
|
|
259
|
-
await Promise.all(startPromises);
|
|
260
|
-
this.initialized = true;
|
|
261
|
-
this.log('Pool initialized');
|
|
262
|
-
}
|
|
263
|
-
handleWorkerExit(worker) {
|
|
264
|
-
// Remove from available list
|
|
265
|
-
const availIdx = this.availableWorkers.indexOf(worker);
|
|
266
|
-
if (availIdx >= 0) {
|
|
267
|
-
this.availableWorkers.splice(availIdx, 1);
|
|
268
|
-
}
|
|
269
|
-
// Try to restart the worker
|
|
270
|
-
this.log(`Worker ${worker.id} exited, attempting restart`);
|
|
271
|
-
const newWorker = this.options.createWorker?.(worker.id, {
|
|
272
|
-
cwd: this.options.cwd,
|
|
273
|
-
vitestConfig: this.options.vitestConfig,
|
|
274
|
-
debug: this.options.debug,
|
|
275
|
-
}) ??
|
|
276
|
-
new VitestWorker(worker.id, this.options.cwd, this.options.vitestConfig, this.options.debug);
|
|
277
|
-
const idx = this.workers.indexOf(worker);
|
|
278
|
-
if (idx >= 0) {
|
|
279
|
-
this.workers[idx] = newWorker;
|
|
280
|
-
}
|
|
281
|
-
newWorker.on('exit', () => {
|
|
282
|
-
if (!this.shuttingDown) {
|
|
283
|
-
this.handleWorkerExit(newWorker);
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
newWorker.start()
|
|
287
|
-
.then(() => {
|
|
288
|
-
this.releaseWorker(newWorker);
|
|
289
|
-
this.log(`Worker ${newWorker.id} restarted`);
|
|
290
|
-
})
|
|
291
|
-
.catch((err) => {
|
|
292
|
-
this.log(`Failed to restart worker ${newWorker.id}: ${err}`);
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
async acquireWorker() {
|
|
296
|
-
// Try to get an available worker
|
|
297
|
-
const worker = this.availableWorkers.shift();
|
|
298
|
-
if (worker) {
|
|
299
|
-
return worker;
|
|
300
|
-
}
|
|
301
|
-
// Wait for one to become available
|
|
302
|
-
return new Promise((resolve) => {
|
|
303
|
-
this.waitingTasks.push(resolve);
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
releaseWorker(worker) {
|
|
307
|
-
// If someone is waiting, give them the worker directly
|
|
308
|
-
const waiting = this.waitingTasks.shift();
|
|
309
|
-
if (waiting) {
|
|
310
|
-
waiting(worker);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
// Otherwise return to the pool
|
|
314
|
-
if (worker.isReady()) {
|
|
315
|
-
this.availableWorkers.push(worker);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
async run(mutant, tests) {
|
|
319
|
-
if (!this.initialized) {
|
|
320
|
-
throw new Error('Pool not initialized. Call init() first.');
|
|
321
|
-
}
|
|
322
|
-
if (this.shuttingDown) {
|
|
323
|
-
throw new Error('Pool is shutting down');
|
|
324
|
-
}
|
|
325
|
-
const worker = await this.acquireWorker();
|
|
326
|
-
try {
|
|
327
|
-
const result = await worker.run(mutant, tests, this.options.timeoutMs);
|
|
328
|
-
return result;
|
|
329
|
-
}
|
|
330
|
-
finally {
|
|
331
|
-
this.releaseWorker(worker);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
async shutdown() {
|
|
335
|
-
if (this.shuttingDown)
|
|
336
|
-
return;
|
|
337
|
-
this.shuttingDown = true;
|
|
338
|
-
this.log('Shutting down pool');
|
|
339
|
-
await Promise.all(this.workers.map((w) => w.shutdown()));
|
|
340
|
-
this.workers = [];
|
|
341
|
-
this.availableWorkers = [];
|
|
342
|
-
this.initialized = false;
|
|
343
|
-
this.log('Pool shut down');
|
|
344
|
-
}
|
|
345
|
-
log(msg) {
|
|
346
|
-
if (this.options.debug) {
|
|
347
|
-
console.error(`[VitestPool] ${msg}`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Run a single mutation using the pool.
|
|
353
|
-
* Convenience function for integration with orchestrator.
|
|
354
|
-
*/
|
|
355
|
-
export async function runWithPool(pool, mutant, tests) {
|
|
356
|
-
try {
|
|
357
|
-
const result = await pool.run(mutant, [...tests]);
|
|
358
|
-
if (result.error === 'timeout') {
|
|
359
|
-
return { status: 'timeout', durationMs: result.durationMs, error: result.error };
|
|
360
|
-
}
|
|
361
|
-
if (result.error && !result.killed) {
|
|
362
|
-
return { status: 'error', durationMs: result.durationMs, error: result.error };
|
|
363
|
-
}
|
|
364
|
-
return {
|
|
365
|
-
status: result.killed ? 'killed' : 'escaped',
|
|
366
|
-
durationMs: result.durationMs,
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
catch (err) {
|
|
370
|
-
return {
|
|
371
|
-
status: 'error',
|
|
372
|
-
durationMs: 0,
|
|
373
|
-
error: err instanceof Error ? err.message : String(err),
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Persistent Vitest worker process.
|
|
3
|
-
*
|
|
4
|
-
* This worker stays alive and receives mutation tasks via stdin,
|
|
5
|
-
* using Vitest's programmatic API to rerun tests without process restart.
|
|
6
|
-
*
|
|
7
|
-
* Communication protocol (JSON-RPC over stdin/stdout):
|
|
8
|
-
*
|
|
9
|
-
* Request: { "type": "run", "mutant": { file, code, id, name }, "tests": string[] }
|
|
10
|
-
* Response: { "type": "result", "killed": boolean, "durationMs": number }
|
|
11
|
-
*
|
|
12
|
-
* Request: { "type": "shutdown" }
|
|
13
|
-
* Response: { "type": "shutdown", "ok": true }
|
|
14
|
-
*/
|
|
15
|
-
export {};
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Persistent Vitest worker process.
|
|
3
|
-
*
|
|
4
|
-
* This worker stays alive and receives mutation tasks via stdin,
|
|
5
|
-
* using Vitest's programmatic API to rerun tests without process restart.
|
|
6
|
-
*
|
|
7
|
-
* Communication protocol (JSON-RPC over stdin/stdout):
|
|
8
|
-
*
|
|
9
|
-
* Request: { "type": "run", "mutant": { file, code, id, name }, "tests": string[] }
|
|
10
|
-
* Response: { "type": "result", "killed": boolean, "durationMs": number }
|
|
11
|
-
*
|
|
12
|
-
* Request: { "type": "shutdown" }
|
|
13
|
-
* Response: { "type": "shutdown", "ok": true }
|
|
14
|
-
*/
|
|
15
|
-
import * as readline from 'node:readline';
|
|
16
|
-
import { createVitestWorkerRuntime } from '../../adapters/vitest/worker-runtime.js';
|
|
17
|
-
// Global state for redirect - shared with the plugin via globalThis
|
|
18
|
-
// Type is declared in pool-plugin.ts
|
|
19
|
-
globalThis.__mutineer_redirect__ = { from: null, to: null };
|
|
20
|
-
function send(response) {
|
|
21
|
-
console.log(JSON.stringify(response));
|
|
22
|
-
}
|
|
23
|
-
function log(msg) {
|
|
24
|
-
if (process.env.MUTINEER_DEBUG !== '1')
|
|
25
|
-
return;
|
|
26
|
-
console.error(`[vitest-worker] ${msg}`);
|
|
27
|
-
}
|
|
28
|
-
async function main() {
|
|
29
|
-
const workerId = process.env.MUTINEER_WORKER_ID ?? 'unknown';
|
|
30
|
-
const cwd = process.env.MUTINEER_CWD ?? process.cwd();
|
|
31
|
-
const vitestConfigPath = process.env.MUTINEER_VITEST_CONFIG;
|
|
32
|
-
log(`Starting worker ${workerId} in ${cwd}`);
|
|
33
|
-
const runtime = createVitestWorkerRuntime({
|
|
34
|
-
workerId,
|
|
35
|
-
cwd,
|
|
36
|
-
vitestConfigPath,
|
|
37
|
-
debug: process.env.MUTINEER_DEBUG === '1',
|
|
38
|
-
});
|
|
39
|
-
try {
|
|
40
|
-
await runtime.init();
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
console.error('[vitest-worker] Failed to initialize Vitest:', err);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
// Signal ready
|
|
47
|
-
send({ type: 'ready', workerId });
|
|
48
|
-
// Process requests from stdin
|
|
49
|
-
const rl = readline.createInterface({
|
|
50
|
-
input: process.stdin,
|
|
51
|
-
terminal: false,
|
|
52
|
-
});
|
|
53
|
-
for await (const line of rl) {
|
|
54
|
-
if (!line.trim())
|
|
55
|
-
continue;
|
|
56
|
-
let request;
|
|
57
|
-
try {
|
|
58
|
-
request = JSON.parse(line);
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
log(`Invalid JSON: ${line}`);
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (request.type === 'shutdown') {
|
|
65
|
-
log('Shutting down');
|
|
66
|
-
await runtime.shutdown();
|
|
67
|
-
send({ type: 'shutdown', ok: true });
|
|
68
|
-
process.exit(0);
|
|
69
|
-
}
|
|
70
|
-
if (request.type === 'run') {
|
|
71
|
-
try {
|
|
72
|
-
const { mutant, tests } = request;
|
|
73
|
-
const result = await runtime.run(mutant, tests);
|
|
74
|
-
send({
|
|
75
|
-
type: 'result',
|
|
76
|
-
killed: result.killed,
|
|
77
|
-
durationMs: result.durationMs,
|
|
78
|
-
error: result.error,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
// On error, treat as killed (conservative)
|
|
83
|
-
send({
|
|
84
|
-
type: 'result',
|
|
85
|
-
killed: true,
|
|
86
|
-
durationMs: 0,
|
|
87
|
-
error: String(err),
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
main().catch((err) => {
|
|
94
|
-
console.error('[vitest-worker] Fatal error:', err);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Persistent Vitest worker process.
|
|
3
|
-
*
|
|
4
|
-
* This worker stays alive and receives mutation tasks via stdin,
|
|
5
|
-
* using Vitest's programmatic API to rerun tests without process restart.
|
|
6
|
-
*
|
|
7
|
-
* Communication protocol (JSON-RPC over stdin/stdout):
|
|
8
|
-
*
|
|
9
|
-
* Request: { "type": "run", "mutant": { file, code, id, name }, "tests": string[] }
|
|
10
|
-
* Response: { "type": "result", "killed": boolean, "durationMs": number }
|
|
11
|
-
*
|
|
12
|
-
* Request: { "type": "shutdown" }
|
|
13
|
-
* Response: { "type": "shutdown", "ok": true }
|
|
14
|
-
*/
|
|
15
|
-
export {};
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Persistent Vitest worker process.
|
|
3
|
-
*
|
|
4
|
-
* This worker stays alive and receives mutation tasks via stdin,
|
|
5
|
-
* using Vitest's programmatic API to rerun tests without process restart.
|
|
6
|
-
*
|
|
7
|
-
* Communication protocol (JSON-RPC over stdin/stdout):
|
|
8
|
-
*
|
|
9
|
-
* Request: { "type": "run", "mutant": { file, code, id, name }, "tests": string[] }
|
|
10
|
-
* Response: { "type": "result", "killed": boolean, "durationMs": number }
|
|
11
|
-
*
|
|
12
|
-
* Request: { "type": "shutdown" }
|
|
13
|
-
* Response: { "type": "shutdown", "ok": true }
|
|
14
|
-
*/
|
|
15
|
-
import * as readline from 'node:readline';
|
|
16
|
-
import { createVitestWorkerRuntime } from '../adapters/vitest-worker-runtime.js';
|
|
17
|
-
// Global state for redirect - shared with the plugin via globalThis
|
|
18
|
-
// Type is declared in pool-plugin.ts
|
|
19
|
-
globalThis.__mutineer_redirect__ = { from: null, to: null };
|
|
20
|
-
function send(response) {
|
|
21
|
-
console.log(JSON.stringify(response));
|
|
22
|
-
}
|
|
23
|
-
function log(msg) {
|
|
24
|
-
if (process.env.MUTINEER_DEBUG !== '1')
|
|
25
|
-
return;
|
|
26
|
-
console.error(`[vitest-worker] ${msg}`);
|
|
27
|
-
}
|
|
28
|
-
async function main() {
|
|
29
|
-
const workerId = process.env.MUTINEER_WORKER_ID ?? 'unknown';
|
|
30
|
-
const cwd = process.env.MUTINEER_CWD ?? process.cwd();
|
|
31
|
-
const vitestConfigPath = process.env.MUTINEER_VITEST_CONFIG;
|
|
32
|
-
log(`Starting worker ${workerId} in ${cwd}`);
|
|
33
|
-
const runtime = createVitestWorkerRuntime({
|
|
34
|
-
workerId,
|
|
35
|
-
cwd,
|
|
36
|
-
vitestConfigPath,
|
|
37
|
-
debug: process.env.MUTINEER_DEBUG === '1',
|
|
38
|
-
});
|
|
39
|
-
try {
|
|
40
|
-
await runtime.init();
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
console.error('[vitest-worker] Failed to initialize Vitest:', err);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
// Signal ready
|
|
47
|
-
send({ type: 'ready', workerId });
|
|
48
|
-
// Process requests from stdin
|
|
49
|
-
const rl = readline.createInterface({
|
|
50
|
-
input: process.stdin,
|
|
51
|
-
terminal: false,
|
|
52
|
-
});
|
|
53
|
-
for await (const line of rl) {
|
|
54
|
-
if (!line.trim())
|
|
55
|
-
continue;
|
|
56
|
-
let request;
|
|
57
|
-
try {
|
|
58
|
-
request = JSON.parse(line);
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
log(`Invalid JSON: ${line}`);
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (request.type === 'shutdown') {
|
|
65
|
-
log('Shutting down');
|
|
66
|
-
await runtime.shutdown();
|
|
67
|
-
send({ type: 'shutdown', ok: true });
|
|
68
|
-
process.exit(0);
|
|
69
|
-
}
|
|
70
|
-
if (request.type === 'run') {
|
|
71
|
-
try {
|
|
72
|
-
const { mutant, tests } = request;
|
|
73
|
-
const result = await runtime.run(mutant, tests);
|
|
74
|
-
send({
|
|
75
|
-
type: 'result',
|
|
76
|
-
killed: result.killed,
|
|
77
|
-
durationMs: result.durationMs,
|
|
78
|
-
error: result.error,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
// On error, treat as killed (conservative)
|
|
83
|
-
send({
|
|
84
|
-
type: 'result',
|
|
85
|
-
killed: true,
|
|
86
|
-
durationMs: 0,
|
|
87
|
-
error: String(err),
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
main().catch((err) => {
|
|
94
|
-
console.error('[vitest-worker] Fatal error:', err);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared module redirection utilities for both Jest and Vitest runners.
|
|
3
|
-
*
|
|
4
|
-
* This module provides common functionality for intercepting module resolution
|
|
5
|
-
* and redirecting imports to mutated versions of source files.
|
|
6
|
-
*/
|
|
7
|
-
export interface MutantRedirect {
|
|
8
|
-
from: string | null;
|
|
9
|
-
to: string | null;
|
|
10
|
-
}
|
|
11
|
-
declare global {
|
|
12
|
-
var __mutineer_redirect__: MutantRedirect | undefined;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Get the path where a mutant should be written.
|
|
16
|
-
* Mutants are stored in __mutineer__/ subdirectories alongside source files.
|
|
17
|
-
*
|
|
18
|
-
* @param originalFile - The original source file path
|
|
19
|
-
* @param mutantId - The mutation ID
|
|
20
|
-
* @returns Path to the mutant file in __mutineer__/ directory
|
|
21
|
-
*/
|
|
22
|
-
export declare function getMutantFilePath(originalFile: string, mutantId: string): string;
|
|
23
|
-
/**
|
|
24
|
-
* Get the current redirect configuration from globalThis.
|
|
25
|
-
* Used by both Jest and Vitest to check if a module should be redirected.
|
|
26
|
-
*
|
|
27
|
-
* @returns Object with normalized from/to paths, or null if no redirect is active
|
|
28
|
-
*/
|
|
29
|
-
export declare function getActiveRedirect(): {
|
|
30
|
-
from: string;
|
|
31
|
-
to: string;
|
|
32
|
-
} | null;
|
|
33
|
-
/**
|
|
34
|
-
* Initialize the global redirect state.
|
|
35
|
-
* Should be called once when the runtime starts.
|
|
36
|
-
*/
|
|
37
|
-
export declare function initializeRedirect(): void;
|
|
38
|
-
/**
|
|
39
|
-
* Set a redirect for module resolution.
|
|
40
|
-
*
|
|
41
|
-
* @param originalFile - The original source file (absolute path)
|
|
42
|
-
* @param mutantPath - The mutant file path
|
|
43
|
-
*/
|
|
44
|
-
export declare function setRedirect(originalFile: string, mutantPath: string): void;
|
|
45
|
-
/**
|
|
46
|
-
* Clear the current redirect.
|
|
47
|
-
*/
|
|
48
|
-
export declare function clearRedirect(): void;
|
|
49
|
-
/**
|
|
50
|
-
* Check if a given file path matches the current redirect source.
|
|
51
|
-
* Handles path normalization and extension matching.
|
|
52
|
-
*
|
|
53
|
-
* @param filePath - The file path to check
|
|
54
|
-
* @returns true if this file should be redirected
|
|
55
|
-
*/
|
|
56
|
-
export declare function shouldRedirectPath(filePath: string): boolean;
|