@mutineerjs/mutineer 0.2.4 → 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/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 +2 -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 +1 -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/utils/__tests__/coverage.spec.js +167 -0
- package/dist/utils/__tests__/progress.spec.js +96 -0
- 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,309 +0,0 @@
|
|
|
1
|
-
import { fork } from 'node:child_process';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { EventEmitter } from 'node:events';
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
class JestWorker extends EventEmitter {
|
|
8
|
-
constructor(id, cwd, jestConfig, debug = false) {
|
|
9
|
-
super();
|
|
10
|
-
this.cwd = cwd;
|
|
11
|
-
this.jestConfig = jestConfig;
|
|
12
|
-
this.debug = debug;
|
|
13
|
-
this.process = null;
|
|
14
|
-
this.pendingTask = null;
|
|
15
|
-
this.ready = false;
|
|
16
|
-
this.shuttingDown = false;
|
|
17
|
-
this.id = id;
|
|
18
|
-
}
|
|
19
|
-
async start() {
|
|
20
|
-
const workerJs = path.join(__dirname, 'jest-worker.js');
|
|
21
|
-
const workerMts = path.join(__dirname, 'jest-worker.mjs');
|
|
22
|
-
const workerTs = path.join(__dirname, 'jest-worker.mts');
|
|
23
|
-
const workerScript = fs.existsSync(workerJs)
|
|
24
|
-
? workerJs
|
|
25
|
-
: fs.existsSync(workerMts)
|
|
26
|
-
? workerMts
|
|
27
|
-
: workerTs;
|
|
28
|
-
const env = {
|
|
29
|
-
...process.env,
|
|
30
|
-
MUTINEER_WORKER_ID: this.id,
|
|
31
|
-
MUTINEER_CWD: this.cwd,
|
|
32
|
-
...(this.jestConfig ? { MUTINEER_JEST_CONFIG: this.jestConfig } : {}),
|
|
33
|
-
...(this.debug ? { MUTINEER_DEBUG: '1' } : {}),
|
|
34
|
-
};
|
|
35
|
-
this.log('Starting Jest worker process');
|
|
36
|
-
this.process = fork(workerScript, [], {
|
|
37
|
-
cwd: this.cwd,
|
|
38
|
-
env,
|
|
39
|
-
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
|
40
|
-
execArgv: ['--experimental-strip-types', '--experimental-transform-types', '--no-warnings'],
|
|
41
|
-
});
|
|
42
|
-
this.process.stderr?.on('data', (data) => {
|
|
43
|
-
if (this.debug) {
|
|
44
|
-
process.stderr.write(`[jest-worker-${this.id}] ${data}`);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
this.process.on('message', (msg) => this.handleMessage(msg));
|
|
48
|
-
this.process.on('error', (err) => {
|
|
49
|
-
this.log(`Process error: ${err.message}`);
|
|
50
|
-
this.handleExit(1);
|
|
51
|
-
});
|
|
52
|
-
this.process.on('exit', (code) => {
|
|
53
|
-
this.log(`Process exited with code ${code}`);
|
|
54
|
-
this.handleExit(code ?? 1);
|
|
55
|
-
});
|
|
56
|
-
await new Promise((resolve, reject) => {
|
|
57
|
-
const timeoutMs = 60_000;
|
|
58
|
-
const timeout = setTimeout(() => {
|
|
59
|
-
reject(new Error(`Worker ${this.id} did not become ready in time (${timeoutMs}ms)`));
|
|
60
|
-
}, timeoutMs);
|
|
61
|
-
this.once('ready', () => {
|
|
62
|
-
clearTimeout(timeout);
|
|
63
|
-
resolve();
|
|
64
|
-
});
|
|
65
|
-
this.once('error', (err) => {
|
|
66
|
-
clearTimeout(timeout);
|
|
67
|
-
reject(err);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
handleMessage(raw) {
|
|
72
|
-
const msg = raw;
|
|
73
|
-
if (msg.type === 'ready') {
|
|
74
|
-
this.ready = true;
|
|
75
|
-
this.emit('ready');
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (msg.type === 'result') {
|
|
79
|
-
if (this.pendingTask) {
|
|
80
|
-
const { resolve, timeoutHandle } = this.pendingTask;
|
|
81
|
-
if (timeoutHandle)
|
|
82
|
-
clearTimeout(timeoutHandle);
|
|
83
|
-
this.pendingTask = null;
|
|
84
|
-
resolve({
|
|
85
|
-
killed: msg.killed ?? true,
|
|
86
|
-
durationMs: msg.durationMs ?? 0,
|
|
87
|
-
error: msg.error,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (msg.type === 'shutdown') {
|
|
93
|
-
this.emit('shutdown');
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
handleExit(code) {
|
|
98
|
-
this.ready = false;
|
|
99
|
-
if (this.pendingTask && !this.shuttingDown) {
|
|
100
|
-
const { reject, timeoutHandle } = this.pendingTask;
|
|
101
|
-
if (timeoutHandle)
|
|
102
|
-
clearTimeout(timeoutHandle);
|
|
103
|
-
this.pendingTask = null;
|
|
104
|
-
reject(new Error(`Worker exited unexpectedly with code ${code}`));
|
|
105
|
-
}
|
|
106
|
-
this.emit('exit', code);
|
|
107
|
-
}
|
|
108
|
-
isReady() {
|
|
109
|
-
return this.ready && this.process !== null && !this.shuttingDown;
|
|
110
|
-
}
|
|
111
|
-
isBusy() {
|
|
112
|
-
return this.pendingTask !== null;
|
|
113
|
-
}
|
|
114
|
-
async run(mutant, tests, timeoutMs = 10_000) {
|
|
115
|
-
if (!this.isReady()) {
|
|
116
|
-
throw new Error(`Worker ${this.id} is not ready`);
|
|
117
|
-
}
|
|
118
|
-
if (this.isBusy()) {
|
|
119
|
-
throw new Error(`Worker ${this.id} is busy`);
|
|
120
|
-
}
|
|
121
|
-
return new Promise((resolve, reject) => {
|
|
122
|
-
const timeoutHandle = setTimeout(() => {
|
|
123
|
-
if (this.pendingTask) {
|
|
124
|
-
this.pendingTask = null;
|
|
125
|
-
this.kill();
|
|
126
|
-
resolve({ killed: true, durationMs: timeoutMs, error: 'timeout' });
|
|
127
|
-
}
|
|
128
|
-
}, timeoutMs);
|
|
129
|
-
this.pendingTask = { resolve, reject, timeoutHandle };
|
|
130
|
-
this.process.send?.({ type: 'run', mutant, tests });
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
async shutdown() {
|
|
134
|
-
if (!this.process || this.shuttingDown)
|
|
135
|
-
return;
|
|
136
|
-
this.shuttingDown = true;
|
|
137
|
-
return new Promise((resolve) => {
|
|
138
|
-
const timeout = setTimeout(() => {
|
|
139
|
-
this.kill();
|
|
140
|
-
resolve();
|
|
141
|
-
}, 5000);
|
|
142
|
-
this.once('shutdown', () => {
|
|
143
|
-
clearTimeout(timeout);
|
|
144
|
-
resolve();
|
|
145
|
-
});
|
|
146
|
-
this.process.send?.({ type: 'shutdown' });
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
kill() {
|
|
150
|
-
if (this.process) {
|
|
151
|
-
try {
|
|
152
|
-
this.process.kill('SIGKILL');
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
// ignore
|
|
156
|
-
}
|
|
157
|
-
this.process = null;
|
|
158
|
-
}
|
|
159
|
-
this.ready = false;
|
|
160
|
-
}
|
|
161
|
-
log(msg) {
|
|
162
|
-
if (this.debug) {
|
|
163
|
-
console.error(`[JestWorker-${this.id}] ${msg}`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
export class JestPool {
|
|
168
|
-
constructor(options) {
|
|
169
|
-
this.workers = [];
|
|
170
|
-
this.availableWorkers = [];
|
|
171
|
-
this.waitingTasks = [];
|
|
172
|
-
this.initialized = false;
|
|
173
|
-
this.shuttingDown = false;
|
|
174
|
-
this.options = {
|
|
175
|
-
cwd: options.cwd,
|
|
176
|
-
concurrency: options.concurrency,
|
|
177
|
-
jestConfig: options.jestConfig,
|
|
178
|
-
timeoutMs: options.timeoutMs ?? 10_000,
|
|
179
|
-
debug: options.debug ?? false,
|
|
180
|
-
createWorker: options.createWorker,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
async init() {
|
|
184
|
-
if (this.initialized)
|
|
185
|
-
return;
|
|
186
|
-
const startPromises = [];
|
|
187
|
-
for (let i = 0; i < this.options.concurrency; i++) {
|
|
188
|
-
const worker = this.options.createWorker?.(`w${i}`, {
|
|
189
|
-
cwd: this.options.cwd,
|
|
190
|
-
jestConfig: this.options.jestConfig,
|
|
191
|
-
debug: this.options.debug,
|
|
192
|
-
}) ??
|
|
193
|
-
new JestWorker(`w${i}`, this.options.cwd, this.options.jestConfig, this.options.debug);
|
|
194
|
-
worker.on('exit', () => {
|
|
195
|
-
if (!this.shuttingDown) {
|
|
196
|
-
this.handleWorkerExit(worker);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
this.workers.push(worker);
|
|
200
|
-
startPromises.push(worker.start().then(() => {
|
|
201
|
-
this.availableWorkers.push(worker);
|
|
202
|
-
}));
|
|
203
|
-
}
|
|
204
|
-
await Promise.all(startPromises);
|
|
205
|
-
this.initialized = true;
|
|
206
|
-
}
|
|
207
|
-
handleWorkerExit(worker) {
|
|
208
|
-
const availIdx = this.availableWorkers.indexOf(worker);
|
|
209
|
-
if (availIdx >= 0) {
|
|
210
|
-
this.availableWorkers.splice(availIdx, 1);
|
|
211
|
-
}
|
|
212
|
-
const newWorker = this.options.createWorker?.(worker.id, {
|
|
213
|
-
cwd: this.options.cwd,
|
|
214
|
-
jestConfig: this.options.jestConfig,
|
|
215
|
-
debug: this.options.debug,
|
|
216
|
-
}) ??
|
|
217
|
-
new JestWorker(worker.id, this.options.cwd, this.options.jestConfig, this.options.debug);
|
|
218
|
-
const idx = this.workers.indexOf(worker);
|
|
219
|
-
if (idx >= 0) {
|
|
220
|
-
this.workers[idx] = newWorker;
|
|
221
|
-
}
|
|
222
|
-
newWorker.on('exit', () => {
|
|
223
|
-
if (!this.shuttingDown) {
|
|
224
|
-
this.handleWorkerExit(newWorker);
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
newWorker.start()
|
|
228
|
-
.then(() => {
|
|
229
|
-
this.releaseWorker(newWorker);
|
|
230
|
-
})
|
|
231
|
-
.catch((err) => {
|
|
232
|
-
this.log(`Failed to restart worker ${newWorker.id}: ${err}`);
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
async acquireWorker() {
|
|
236
|
-
const worker = this.availableWorkers.shift();
|
|
237
|
-
if (worker) {
|
|
238
|
-
return worker;
|
|
239
|
-
}
|
|
240
|
-
return new Promise((resolve) => {
|
|
241
|
-
this.waitingTasks.push(resolve);
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
releaseWorker(worker) {
|
|
245
|
-
const waiting = this.waitingTasks.shift();
|
|
246
|
-
if (waiting) {
|
|
247
|
-
waiting(worker);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
if (worker.isReady()) {
|
|
251
|
-
this.availableWorkers.push(worker);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
async run(mutant, tests) {
|
|
255
|
-
if (!this.initialized) {
|
|
256
|
-
throw new Error('Pool not initialized. Call init() first.');
|
|
257
|
-
}
|
|
258
|
-
if (this.shuttingDown) {
|
|
259
|
-
throw new Error('Pool is shutting down');
|
|
260
|
-
}
|
|
261
|
-
const worker = await this.acquireWorker();
|
|
262
|
-
try {
|
|
263
|
-
const result = await worker.run(mutant, tests, this.options.timeoutMs);
|
|
264
|
-
if (this.options.debug) {
|
|
265
|
-
console.error(`[JestPool] worker ${worker.id} returned killed=${result.killed} error=${result.error ?? 'none'} duration=${result.durationMs}`);
|
|
266
|
-
}
|
|
267
|
-
return result;
|
|
268
|
-
}
|
|
269
|
-
finally {
|
|
270
|
-
this.releaseWorker(worker);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
async shutdown() {
|
|
274
|
-
if (this.shuttingDown)
|
|
275
|
-
return;
|
|
276
|
-
this.shuttingDown = true;
|
|
277
|
-
await Promise.all(this.workers.map((w) => w.shutdown()));
|
|
278
|
-
this.workers = [];
|
|
279
|
-
this.availableWorkers = [];
|
|
280
|
-
this.initialized = false;
|
|
281
|
-
}
|
|
282
|
-
log(msg) {
|
|
283
|
-
if (this.options.debug) {
|
|
284
|
-
console.error(`[JestPool] ${msg}`);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
export async function runWithJestPool(pool, mutant, tests) {
|
|
289
|
-
try {
|
|
290
|
-
const result = await pool.run(mutant, [...tests]);
|
|
291
|
-
if (result.error === 'timeout') {
|
|
292
|
-
return { status: 'timeout', durationMs: result.durationMs, error: result.error };
|
|
293
|
-
}
|
|
294
|
-
if (result.error && !result.killed) {
|
|
295
|
-
return { status: 'error', durationMs: result.durationMs, error: result.error };
|
|
296
|
-
}
|
|
297
|
-
return {
|
|
298
|
-
status: result.killed ? 'killed' : 'escaped',
|
|
299
|
-
durationMs: result.durationMs,
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
catch (err) {
|
|
303
|
-
return {
|
|
304
|
-
status: 'error',
|
|
305
|
-
durationMs: 0,
|
|
306
|
-
error: err instanceof Error ? err.message : String(err),
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { createJestWorkerRuntime } from '../adapters/jest-worker-runtime.js';
|
|
2
|
-
globalThis.__mutineer_redirect__ = { from: null, to: null };
|
|
3
|
-
function log(msg) {
|
|
4
|
-
if (process.env.MUTINEER_DEBUG !== '1')
|
|
5
|
-
return;
|
|
6
|
-
console.error(`[jest-worker] ${msg}`);
|
|
7
|
-
}
|
|
8
|
-
async function main() {
|
|
9
|
-
const workerId = process.env.MUTINEER_WORKER_ID ?? 'unknown';
|
|
10
|
-
const cwd = process.env.MUTINEER_CWD ?? process.cwd();
|
|
11
|
-
const jestConfigPath = process.env.MUTINEER_JEST_CONFIG;
|
|
12
|
-
const debug = process.env.MUTINEER_DEBUG === '1';
|
|
13
|
-
log(`Starting worker ${workerId} in ${cwd}`);
|
|
14
|
-
const runtime = createJestWorkerRuntime({
|
|
15
|
-
workerId,
|
|
16
|
-
cwd,
|
|
17
|
-
jestConfigPath,
|
|
18
|
-
debug,
|
|
19
|
-
});
|
|
20
|
-
try {
|
|
21
|
-
await runtime.init();
|
|
22
|
-
}
|
|
23
|
-
catch (err) {
|
|
24
|
-
console.error('[jest-worker] Failed to initialize:', err);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
process.send?.({ type: 'ready', workerId });
|
|
28
|
-
process.on('message', async (raw) => {
|
|
29
|
-
if (raw.type === 'shutdown') {
|
|
30
|
-
log('Shutting down');
|
|
31
|
-
await runtime.shutdown();
|
|
32
|
-
process.send?.({ type: 'shutdown', ok: true });
|
|
33
|
-
process.exit(0);
|
|
34
|
-
}
|
|
35
|
-
if (raw.type === 'run') {
|
|
36
|
-
try {
|
|
37
|
-
const { mutant, tests } = raw;
|
|
38
|
-
const result = await runtime.run(mutant, tests);
|
|
39
|
-
process.send?.({
|
|
40
|
-
type: 'result',
|
|
41
|
-
killed: result.killed,
|
|
42
|
-
durationMs: result.durationMs,
|
|
43
|
-
error: result.error,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
catch (err) {
|
|
47
|
-
process.send?.({
|
|
48
|
-
type: 'result',
|
|
49
|
-
killed: true,
|
|
50
|
-
durationMs: 0,
|
|
51
|
-
error: String(err),
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
main().catch((err) => {
|
|
58
|
-
console.error('[jest-worker] Fatal error:', err);
|
|
59
|
-
process.exit(1);
|
|
60
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vite plugin for persistent Vitest workers.
|
|
3
|
-
*
|
|
4
|
-
* Unlike the standard viteMutineerPlugin which reads env vars once at init,
|
|
5
|
-
* this plugin reads from a global redirect map that can be updated dynamically
|
|
6
|
-
* between test runs.
|
|
7
|
-
*
|
|
8
|
-
* The worker process sets globalThis.__mutineer_redirect__ before each test run,
|
|
9
|
-
* and this plugin intercepts module loading to return the mutated code.
|
|
10
|
-
*/
|
|
11
|
-
import type { PluginOption } from 'vite';
|
|
12
|
-
declare global {
|
|
13
|
-
var __mutineer_redirect__: {
|
|
14
|
-
from: string | null;
|
|
15
|
-
to: string | null;
|
|
16
|
-
} | undefined;
|
|
17
|
-
}
|
|
18
|
-
export declare function poolMutineerPlugin(): PluginOption;
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vite plugin for persistent Vitest workers.
|
|
3
|
-
*
|
|
4
|
-
* Unlike the standard viteMutineerPlugin which reads env vars once at init,
|
|
5
|
-
* this plugin reads from a global redirect map that can be updated dynamically
|
|
6
|
-
* between test runs.
|
|
7
|
-
*
|
|
8
|
-
* The worker process sets globalThis.__mutineer_redirect__ before each test run,
|
|
9
|
-
* and this plugin intercepts module loading to return the mutated code.
|
|
10
|
-
*/
|
|
11
|
-
import * as fs from 'node:fs';
|
|
12
|
-
import * as path from 'node:path';
|
|
13
|
-
/**
|
|
14
|
-
* Get the current redirect config from globalThis.
|
|
15
|
-
* Returns null if no redirect is active.
|
|
16
|
-
*/
|
|
17
|
-
function getRedirect() {
|
|
18
|
-
const redirect = globalThis.__mutineer_redirect__;
|
|
19
|
-
if (!redirect?.from || !redirect?.to) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
return {
|
|
23
|
-
from: path.resolve(redirect.from),
|
|
24
|
-
to: redirect.to,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
export function poolMutineerPlugin() {
|
|
28
|
-
return {
|
|
29
|
-
name: 'mutineer:swap',
|
|
30
|
-
enforce: 'pre',
|
|
31
|
-
load(id) {
|
|
32
|
-
const redirect = getRedirect();
|
|
33
|
-
if (!redirect) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
// Normalize the module ID, handling query strings
|
|
37
|
-
const cleanId = id.split('?')[0];
|
|
38
|
-
let normalizedId;
|
|
39
|
-
try {
|
|
40
|
-
normalizedId = path.resolve(cleanId);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
// Check if this is the file we're redirecting
|
|
46
|
-
if (normalizedId === redirect.from) {
|
|
47
|
-
// Read the mutated code from the temp file
|
|
48
|
-
try {
|
|
49
|
-
const mutatedCode = fs.readFileSync(redirect.to, 'utf8');
|
|
50
|
-
return mutatedCode;
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
console.error(`[mutineer:swap] Failed to read mutant file: ${redirect.to}`, err);
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vite plugin for persistent Vitest workers.
|
|
3
|
-
*
|
|
4
|
-
* Unlike the standard viteMutineerPlugin which reads env vars once at init,
|
|
5
|
-
* this plugin reads from a global redirect map that can be updated dynamically
|
|
6
|
-
* between test runs.
|
|
7
|
-
*
|
|
8
|
-
* The worker process sets globalThis.__mutineer_redirect__ before each test run,
|
|
9
|
-
* and this plugin intercepts module loading to return the mutated code.
|
|
10
|
-
*/
|
|
11
|
-
import type { PluginOption } from 'vite';
|
|
12
|
-
declare global {
|
|
13
|
-
var __mutineer_redirect__: {
|
|
14
|
-
from: string | null;
|
|
15
|
-
to: string | null;
|
|
16
|
-
} | undefined;
|
|
17
|
-
}
|
|
18
|
-
export declare function poolMutineerPlugin(): PluginOption;
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vite plugin for persistent Vitest workers.
|
|
3
|
-
*
|
|
4
|
-
* Unlike the standard viteMutineerPlugin which reads env vars once at init,
|
|
5
|
-
* this plugin reads from a global redirect map that can be updated dynamically
|
|
6
|
-
* between test runs.
|
|
7
|
-
*
|
|
8
|
-
* The worker process sets globalThis.__mutineer_redirect__ before each test run,
|
|
9
|
-
* and this plugin intercepts module loading to return the mutated code.
|
|
10
|
-
*/
|
|
11
|
-
import * as fs from 'node:fs';
|
|
12
|
-
import * as path from 'node:path';
|
|
13
|
-
/**
|
|
14
|
-
* Get the current redirect config from globalThis.
|
|
15
|
-
* Returns null if no redirect is active.
|
|
16
|
-
*/
|
|
17
|
-
function getRedirect() {
|
|
18
|
-
const redirect = globalThis.__mutineer_redirect__;
|
|
19
|
-
if (!redirect?.from || !redirect?.to) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
return {
|
|
23
|
-
from: path.resolve(redirect.from),
|
|
24
|
-
to: redirect.to,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
export function poolMutineerPlugin() {
|
|
28
|
-
return {
|
|
29
|
-
name: 'mutineer:swap',
|
|
30
|
-
enforce: 'pre',
|
|
31
|
-
load(id) {
|
|
32
|
-
const redirect = getRedirect();
|
|
33
|
-
if (!redirect) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
// Normalize the module ID, handling query strings
|
|
37
|
-
const cleanId = id.split('?')[0];
|
|
38
|
-
let normalizedId;
|
|
39
|
-
try {
|
|
40
|
-
normalizedId = path.resolve(cleanId);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
// Check if this is the file we're redirecting
|
|
46
|
-
if (normalizedId === redirect.from) {
|
|
47
|
-
// Read the mutated code from the temp file
|
|
48
|
-
try {
|
|
49
|
-
const mutatedCode = fs.readFileSync(redirect.to, 'utf8');
|
|
50
|
-
return mutatedCode;
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
console.error(`[mutineer:swap] Failed to read mutant file: ${redirect.to}`, err);
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dynamic ESM loader for persistent Vitest workers.
|
|
3
|
-
*
|
|
4
|
-
* Reads redirect targets from globalThis.__mutineer_redirect__ on each resolution
|
|
5
|
-
* so workers can swap files without restarting.
|
|
6
|
-
*/
|
|
7
|
-
declare global {
|
|
8
|
-
var __mutineer_redirect__: {
|
|
9
|
-
from: string | null;
|
|
10
|
-
to: string | null;
|
|
11
|
-
} | undefined;
|
|
12
|
-
}
|
|
13
|
-
export declare function initialize(data: {
|
|
14
|
-
debug?: boolean;
|
|
15
|
-
} | undefined): void;
|
|
16
|
-
export declare function resolve(specifier: string, context: {
|
|
17
|
-
parentURL?: string;
|
|
18
|
-
}, nextResolve: (specifier: string, context: any) => Promise<any>): Promise<any>;
|
|
19
|
-
export declare function load(url: string, context: unknown, nextLoad: (u: string, c: unknown) => Promise<unknown>): Promise<unknown>;
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dynamic ESM loader for persistent Vitest workers.
|
|
3
|
-
*
|
|
4
|
-
* Reads redirect targets from globalThis.__mutineer_redirect__ on each resolution
|
|
5
|
-
* so workers can swap files without restarting.
|
|
6
|
-
*/
|
|
7
|
-
import { register, builtinModules } from 'node:module';
|
|
8
|
-
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
9
|
-
import path from 'node:path';
|
|
10
|
-
import fs from 'node:fs';
|
|
11
|
-
// Register this file as the loader hooks module
|
|
12
|
-
register(import.meta.url, {
|
|
13
|
-
parentURL: import.meta.url,
|
|
14
|
-
data: { debug: process.env.MUTINEER_DEBUG === '1' }
|
|
15
|
-
});
|
|
16
|
-
let DEBUG = process.env.MUTINEER_DEBUG === '1';
|
|
17
|
-
export function initialize(data) {
|
|
18
|
-
if (data?.debug !== undefined) {
|
|
19
|
-
DEBUG = data.debug;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Try to resolve a .js import to a .ts or .tsx file (TypeScript ESM convention)
|
|
24
|
-
*/
|
|
25
|
-
function tryResolveTsExtension(specifier, parentURL) {
|
|
26
|
-
if (!specifier.endsWith('.js') || !specifier.startsWith('.')) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
let parentPath;
|
|
30
|
-
try {
|
|
31
|
-
parentPath = fileURLToPath(parentURL ?? '');
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
const parentDir = path.dirname(parentPath);
|
|
37
|
-
// If the parent is in a __mutineer__ directory, also try the parent's parent
|
|
38
|
-
const dirsToTry = [parentDir];
|
|
39
|
-
if (path.basename(parentDir) === '__mutineer__') {
|
|
40
|
-
dirsToTry.push(path.dirname(parentDir));
|
|
41
|
-
}
|
|
42
|
-
const tsSpecifier = specifier.slice(0, -3) + '.ts';
|
|
43
|
-
const tsxSpecifier = specifier.slice(0, -3) + '.tsx';
|
|
44
|
-
for (const dir of dirsToTry) {
|
|
45
|
-
const tsPath = path.resolve(dir, tsSpecifier);
|
|
46
|
-
if (fs.existsSync(tsPath)) {
|
|
47
|
-
return pathToFileURL(tsPath).href;
|
|
48
|
-
}
|
|
49
|
-
const tsxPath = path.resolve(dir, tsxSpecifier);
|
|
50
|
-
if (fs.existsSync(tsxPath)) {
|
|
51
|
-
return pathToFileURL(tsxPath).href;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
function getRedirect() {
|
|
57
|
-
const redirect = globalThis.__mutineer_redirect__;
|
|
58
|
-
if (!redirect?.from || !redirect?.to) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
from: path.resolve(redirect.from),
|
|
63
|
-
fromUrl: pathToFileURL(path.resolve(redirect.from)).href,
|
|
64
|
-
to: redirect.to,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
export async function resolve(specifier, context, nextResolve) {
|
|
68
|
-
const redirect = getRedirect();
|
|
69
|
-
const isBuiltin = specifier.startsWith('node:') || builtinModules.includes(specifier);
|
|
70
|
-
const isNodeModulesSpec = specifier.includes('node_modules');
|
|
71
|
-
const shouldLog = DEBUG && !isBuiltin && !isNodeModulesSpec;
|
|
72
|
-
if (shouldLog) {
|
|
73
|
-
console.error(`[pool-loader] resolve: ${specifier}`);
|
|
74
|
-
if (redirect) {
|
|
75
|
-
console.error(`[pool-loader] active redirect: ${redirect.from} -> ${redirect.to}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
// Try to resolve .js -> .ts for TypeScript ESM imports
|
|
79
|
-
const tsResolved = tryResolveTsExtension(specifier, context.parentURL);
|
|
80
|
-
if (tsResolved) {
|
|
81
|
-
if (shouldLog)
|
|
82
|
-
console.error(`[pool-loader] .js -> .ts: ${specifier} -> ${tsResolved}`);
|
|
83
|
-
// Check if this is our redirect target
|
|
84
|
-
if (redirect && tsResolved === redirect.fromUrl) {
|
|
85
|
-
if (DEBUG)
|
|
86
|
-
console.error(`[pool-loader] REDIRECTING ${tsResolved} -> ${pathToFileURL(redirect.to).href}`);
|
|
87
|
-
return {
|
|
88
|
-
url: pathToFileURL(redirect.to).href,
|
|
89
|
-
shortCircuit: true
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
url: tsResolved,
|
|
94
|
-
shortCircuit: true
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
const resolved = await nextResolve(specifier, context);
|
|
98
|
-
const resolvedInNodeModules = resolved?.url?.includes('/node_modules/');
|
|
99
|
-
const resolvedBuiltin = resolved?.url?.startsWith('node:');
|
|
100
|
-
if (shouldLog && !resolvedInNodeModules && !resolvedBuiltin)
|
|
101
|
-
console.error(`[pool-loader] resolved ${specifier} to ${resolved.url}`);
|
|
102
|
-
// Check if this resolves to our redirect target
|
|
103
|
-
if (redirect && resolved.url === redirect.fromUrl) {
|
|
104
|
-
if (DEBUG)
|
|
105
|
-
console.error(`[pool-loader] REDIRECTING ${resolved.url} -> ${pathToFileURL(redirect.to).href}`);
|
|
106
|
-
return {
|
|
107
|
-
...resolved,
|
|
108
|
-
url: pathToFileURL(redirect.to).href,
|
|
109
|
-
shortCircuit: true
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
return resolved;
|
|
113
|
-
}
|
|
114
|
-
export async function load(url, context, nextLoad) {
|
|
115
|
-
return nextLoad(url, context);
|
|
116
|
-
}
|