@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
|
@@ -2,7 +2,6 @@ import { describe, it, expect, vi } from 'vitest';
|
|
|
2
2
|
import { JestPool, runWithJestPool } from '../pool.js';
|
|
3
3
|
// We'll use the createWorker option to inject mock workers instead of forking processes
|
|
4
4
|
function makeMockWorker(id) {
|
|
5
|
-
let readyResolve = null;
|
|
6
5
|
const worker = {
|
|
7
6
|
id,
|
|
8
7
|
_ready: true,
|
|
@@ -33,9 +32,9 @@ const dummyMutant = {
|
|
|
33
32
|
describe('JestPool', () => {
|
|
34
33
|
it('throws if run is called before init', async () => {
|
|
35
34
|
const pool = new JestPool({ cwd: '/tmp', concurrency: 1 });
|
|
36
|
-
await expect(pool.run(dummyMutant, ['test.ts'])).rejects.toThrow('Pool not
|
|
35
|
+
await expect(pool.run(dummyMutant, ['test.ts'])).rejects.toThrow('Pool not initialised');
|
|
37
36
|
});
|
|
38
|
-
it('
|
|
37
|
+
it('initialises with the specified concurrency', async () => {
|
|
39
38
|
const workers = [];
|
|
40
39
|
const pool = new JestPool({
|
|
41
40
|
cwd: '/tmp',
|
|
@@ -51,7 +50,7 @@ describe('JestPool', () => {
|
|
|
51
50
|
expect(workers[0].start).toHaveBeenCalled();
|
|
52
51
|
expect(workers[1].start).toHaveBeenCalled();
|
|
53
52
|
});
|
|
54
|
-
it('does not re-
|
|
53
|
+
it('does not re-initialise if already initialised', async () => {
|
|
55
54
|
const workers = [];
|
|
56
55
|
const pool = new JestPool({
|
|
57
56
|
cwd: '/tmp',
|
|
@@ -108,8 +107,8 @@ describe('JestPool', () => {
|
|
|
108
107
|
});
|
|
109
108
|
await pool.init();
|
|
110
109
|
await pool.shutdown();
|
|
111
|
-
// After shutdown,
|
|
112
|
-
await expect(pool.run(dummyMutant, ['test.ts'])).rejects.toThrow('Pool not
|
|
110
|
+
// After shutdown, initialised is set to false, so "not initialised" check fires first
|
|
111
|
+
await expect(pool.run(dummyMutant, ['test.ts'])).rejects.toThrow('Pool not initialised');
|
|
113
112
|
});
|
|
114
113
|
it('does not double-shutdown', async () => {
|
|
115
114
|
const workers = [];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { JestWorkerRuntime, createJestWorkerRuntime } from '../worker-runtime.js';
|
|
2
|
+
import { JestWorkerRuntime, createJestWorkerRuntime, } from '../worker-runtime.js';
|
|
3
3
|
// Mock the shared utilities
|
|
4
4
|
vi.mock('../../shared/index.js', () => ({
|
|
5
|
-
getMutantFilePath: vi.fn((
|
|
5
|
+
getMutantFilePath: vi.fn((id) => `/tmp/__mutineer__/mutant_${id}.ts`),
|
|
6
6
|
setRedirect: vi.fn(),
|
|
7
7
|
clearRedirect: vi.fn(),
|
|
8
8
|
}));
|
|
@@ -121,7 +121,7 @@ export class JestAdapter {
|
|
|
121
121
|
}
|
|
122
122
|
async runMutant(mutant, tests) {
|
|
123
123
|
if (!this.pool) {
|
|
124
|
-
throw new Error('JestAdapter not
|
|
124
|
+
throw new Error('JestAdapter not initialised. Call init() first.');
|
|
125
125
|
}
|
|
126
126
|
try {
|
|
127
127
|
const result = await this.pool.run(mutant, [...tests]);
|
package/dist/runner/jest/pool.js
CHANGED
|
@@ -170,7 +170,7 @@ export class JestPool {
|
|
|
170
170
|
this.workers = [];
|
|
171
171
|
this.availableWorkers = [];
|
|
172
172
|
this.waitingTasks = [];
|
|
173
|
-
this.
|
|
173
|
+
this.initialised = false;
|
|
174
174
|
this.shuttingDown = false;
|
|
175
175
|
this.options = {
|
|
176
176
|
cwd: options.cwd,
|
|
@@ -181,7 +181,7 @@ export class JestPool {
|
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
183
|
async init() {
|
|
184
|
-
if (this.
|
|
184
|
+
if (this.initialised)
|
|
185
185
|
return;
|
|
186
186
|
const startPromises = [];
|
|
187
187
|
for (let i = 0; i < this.options.concurrency; i++) {
|
|
@@ -200,7 +200,7 @@ export class JestPool {
|
|
|
200
200
|
}));
|
|
201
201
|
}
|
|
202
202
|
await Promise.all(startPromises);
|
|
203
|
-
this.
|
|
203
|
+
this.initialised = true;
|
|
204
204
|
}
|
|
205
205
|
handleWorkerExit(worker) {
|
|
206
206
|
const availIdx = this.availableWorkers.indexOf(worker);
|
|
@@ -249,8 +249,8 @@ export class JestPool {
|
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
async run(mutant, tests) {
|
|
252
|
-
if (!this.
|
|
253
|
-
throw new Error('Pool not
|
|
252
|
+
if (!this.initialised) {
|
|
253
|
+
throw new Error('Pool not initialised. Call init() first.');
|
|
254
254
|
}
|
|
255
255
|
if (this.shuttingDown) {
|
|
256
256
|
throw new Error('Pool is shutting down');
|
|
@@ -272,7 +272,7 @@ export class JestPool {
|
|
|
272
272
|
await Promise.all(this.workers.map((w) => w.shutdown()));
|
|
273
273
|
this.workers = [];
|
|
274
274
|
this.availableWorkers = [];
|
|
275
|
-
this.
|
|
275
|
+
this.initialised = false;
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
export async function runWithJestPool(pool, mutant, tests) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import { render } from 'ink';
|
|
2
3
|
import { createElement } from 'react';
|
|
3
4
|
import { Progress } from '../utils/progress.js';
|
|
@@ -42,7 +43,7 @@ export async function executePool(opts) {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
};
|
|
45
|
-
//
|
|
46
|
+
// Initialise worker pool
|
|
46
47
|
const workerLogSuffix = workerCount < concurrency ? ` (requested ${concurrency})` : '';
|
|
47
48
|
log.info(`Initializing ${adapter.name} worker pool with ${workerCount} workers...${workerLogSuffix}`);
|
|
48
49
|
const poolStart = Date.now();
|
|
@@ -93,12 +94,31 @@ export async function executePool(opts) {
|
|
|
93
94
|
col: v.col,
|
|
94
95
|
}, tests);
|
|
95
96
|
const status = result.status;
|
|
97
|
+
let originalSnippet;
|
|
98
|
+
let mutatedSnippet;
|
|
99
|
+
if (status === 'escaped') {
|
|
100
|
+
try {
|
|
101
|
+
const originalLines = fs.readFileSync(v.file, 'utf8').split('\n');
|
|
102
|
+
const mutatedLines = v.code.split('\n');
|
|
103
|
+
const lineIdx = v.line - 1;
|
|
104
|
+
const orig = originalLines[lineIdx]?.trim();
|
|
105
|
+
const mutated = mutatedLines[lineIdx]?.trim();
|
|
106
|
+
if (orig !== undefined && mutated !== undefined && orig !== mutated) {
|
|
107
|
+
originalSnippet = orig;
|
|
108
|
+
mutatedSnippet = mutated;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// best-effort
|
|
113
|
+
}
|
|
114
|
+
}
|
|
96
115
|
cache[key] = {
|
|
97
116
|
status,
|
|
98
117
|
file: v.file,
|
|
99
118
|
line: v.line,
|
|
100
119
|
col: v.col,
|
|
101
120
|
mutator: v.name,
|
|
121
|
+
...(originalSnippet !== undefined && { originalSnippet, mutatedSnippet }),
|
|
102
122
|
};
|
|
103
123
|
progress.update(status);
|
|
104
124
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { initialiseRedirectState, setRedirect, getRedirect, clearRedirect, } from '../redirect-state.js';
|
|
3
3
|
describe('redirect-state', () => {
|
|
4
4
|
beforeEach(() => {
|
|
5
|
-
|
|
5
|
+
initialiseRedirectState();
|
|
6
6
|
});
|
|
7
|
-
describe('
|
|
7
|
+
describe('initialiseRedirectState', () => {
|
|
8
8
|
it('sets global redirect to null/null', () => {
|
|
9
9
|
expect(globalThis.__mutineer_redirect__).toEqual({
|
|
10
10
|
from: null,
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* including mutant file path generation and redirect state management.
|
|
6
6
|
*/
|
|
7
7
|
export { getMutantFilePath } from './mutant-paths.js';
|
|
8
|
-
export { setRedirect, getRedirect, clearRedirect,
|
|
8
|
+
export { setRedirect, getRedirect, clearRedirect, initialiseRedirectState, } from './redirect-state.js';
|
|
9
9
|
export type { RedirectConfig } from './redirect-state.js';
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* including mutant file path generation and redirect state management.
|
|
6
6
|
*/
|
|
7
7
|
export { getMutantFilePath } from './mutant-paths.js';
|
|
8
|
-
export { setRedirect, getRedirect, clearRedirect,
|
|
8
|
+
export { setRedirect, getRedirect, clearRedirect, initialiseRedirectState, } from './redirect-state.js';
|
|
@@ -23,10 +23,10 @@ export interface RedirectConfig {
|
|
|
23
23
|
readonly to: string;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Initialise the global redirect state.
|
|
27
27
|
* Must be called once at module load time.
|
|
28
28
|
*/
|
|
29
|
-
export declare function
|
|
29
|
+
export declare function initialiseRedirectState(): void;
|
|
30
30
|
/**
|
|
31
31
|
* Set the active redirect configuration.
|
|
32
32
|
*
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* test execution.
|
|
9
9
|
*/
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Initialise the global redirect state.
|
|
12
12
|
* Must be called once at module load time.
|
|
13
13
|
*/
|
|
14
|
-
export function
|
|
14
|
+
export function initialiseRedirectState() {
|
|
15
15
|
globalThis.__mutineer_redirect__ = { from: null, to: null };
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
@@ -46,5 +46,5 @@ export function getRedirect() {
|
|
|
46
46
|
export function clearRedirect() {
|
|
47
47
|
globalThis.__mutineer_redirect__ = { from: null, to: null };
|
|
48
48
|
}
|
|
49
|
-
//
|
|
50
|
-
|
|
49
|
+
// Initialise on module load
|
|
50
|
+
initialiseRedirectState();
|
package/dist/runner/types.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface TestRunnerAdapter {
|
|
|
41
41
|
*/
|
|
42
42
|
readonly name: string;
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* Initialise the adapter (start worker pools, etc.).
|
|
45
45
|
* Must be called before running tests.
|
|
46
46
|
*/
|
|
47
47
|
init(concurrencyOverride?: number): Promise<void>;
|
|
@@ -41,7 +41,7 @@ describe('Vitest adapter', () => {
|
|
|
41
41
|
afterEach(() => {
|
|
42
42
|
vi.useRealTimers();
|
|
43
43
|
});
|
|
44
|
-
it('
|
|
44
|
+
it('initialises pool with override concurrency', async () => {
|
|
45
45
|
const adapter = makeAdapter();
|
|
46
46
|
await adapter.init(5);
|
|
47
47
|
expect(poolInstance?.init).toHaveBeenCalledTimes(1);
|
|
@@ -40,7 +40,7 @@ describe('VitestPool', () => {
|
|
|
40
40
|
cwd: process.cwd(),
|
|
41
41
|
concurrency: 1,
|
|
42
42
|
timeoutMs: 5000,
|
|
43
|
-
createWorker: (id
|
|
43
|
+
createWorker: (id) => {
|
|
44
44
|
const worker = new EventEmitter();
|
|
45
45
|
worker.id = id;
|
|
46
46
|
worker.start = vi.fn().mockResolvedValue(undefined);
|
|
@@ -7,11 +7,12 @@ vi.mock('node:module', async (importOriginal) => {
|
|
|
7
7
|
const actual = await importOriginal();
|
|
8
8
|
return { ...actual, register: vi.fn() };
|
|
9
9
|
});
|
|
10
|
-
import { resolve as poolResolve } from '../redirect-loader.js';
|
|
10
|
+
import { resolve as poolResolve, initialise } from '../redirect-loader.js';
|
|
11
11
|
describe('pool-redirect-loader resolve', () => {
|
|
12
12
|
afterEach(() => {
|
|
13
13
|
;
|
|
14
14
|
globalThis.__mutineer_redirect__ = undefined;
|
|
15
|
+
delete process.env.MUTINEER_DEBUG;
|
|
15
16
|
vi.restoreAllMocks();
|
|
16
17
|
});
|
|
17
18
|
it('resolves .js to .ts in the same directory', async () => {
|
|
@@ -88,4 +89,85 @@ describe('pool-redirect-loader resolve', () => {
|
|
|
88
89
|
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
89
90
|
}
|
|
90
91
|
});
|
|
92
|
+
it('passes through non-.js specifiers to nextResolve', async () => {
|
|
93
|
+
const nextResolve = vi.fn().mockResolvedValue({
|
|
94
|
+
url: 'file:///some/module.ts',
|
|
95
|
+
shortCircuit: false,
|
|
96
|
+
});
|
|
97
|
+
const result = await poolResolve('./module', { parentURL: 'file:///src/index.ts' }, nextResolve);
|
|
98
|
+
expect(nextResolve).toHaveBeenCalled();
|
|
99
|
+
expect(result.url).toBe('file:///some/module.ts');
|
|
100
|
+
});
|
|
101
|
+
it('passes through builtin modules to nextResolve', async () => {
|
|
102
|
+
const nextResolve = vi.fn().mockResolvedValue({
|
|
103
|
+
url: 'node:fs',
|
|
104
|
+
shortCircuit: true,
|
|
105
|
+
});
|
|
106
|
+
const result = await poolResolve('node:fs', { parentURL: 'file:///src/index.ts' }, nextResolve);
|
|
107
|
+
expect(nextResolve).toHaveBeenCalled();
|
|
108
|
+
expect(result.url).toBe('node:fs');
|
|
109
|
+
});
|
|
110
|
+
it('skips .js->ts resolution for non-relative specifiers', async () => {
|
|
111
|
+
const nextResolve = vi.fn().mockResolvedValue({
|
|
112
|
+
url: 'file:///node_modules/pkg/index.js',
|
|
113
|
+
shortCircuit: false,
|
|
114
|
+
});
|
|
115
|
+
const result = await poolResolve('some-package/foo.js', { parentURL: 'file:///src/index.ts' }, nextResolve);
|
|
116
|
+
expect(nextResolve).toHaveBeenCalled();
|
|
117
|
+
expect(result.url).toBe('file:///node_modules/pkg/index.js');
|
|
118
|
+
});
|
|
119
|
+
it('resolves .js to .tsx when .ts does not exist', async () => {
|
|
120
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-pool-loader-'));
|
|
121
|
+
const parentFile = path.join(tmpDir, 'src', 'index.ts');
|
|
122
|
+
const tsxFile = path.join(tmpDir, 'src', 'comp.tsx');
|
|
123
|
+
await fs.mkdir(path.dirname(parentFile), { recursive: true });
|
|
124
|
+
await fs.writeFile(parentFile, 'export {}', 'utf8');
|
|
125
|
+
await fs.writeFile(tsxFile, 'export const Comp = () => null', 'utf8');
|
|
126
|
+
try {
|
|
127
|
+
const nextResolve = vi.fn();
|
|
128
|
+
const result = await poolResolve('./comp.js', { parentURL: pathToFileURL(parentFile).href }, nextResolve);
|
|
129
|
+
expect(nextResolve).not.toHaveBeenCalled();
|
|
130
|
+
expect(result.shortCircuit).toBe(true);
|
|
131
|
+
expect(result.url).toBe(pathToFileURL(tsxFile).href);
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
it('tries parent of __mutineer__ directory for ts resolution', async () => {
|
|
138
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-pool-loader-'));
|
|
139
|
+
const srcDir = path.join(tmpDir, 'src');
|
|
140
|
+
const mutineerDir = path.join(srcDir, '__mutineer__');
|
|
141
|
+
await fs.mkdir(mutineerDir, { recursive: true });
|
|
142
|
+
const parentFile = path.join(mutineerDir, 'mutant.ts');
|
|
143
|
+
const tsFile = path.join(srcDir, 'sibling.ts');
|
|
144
|
+
await fs.writeFile(parentFile, 'export {}', 'utf8');
|
|
145
|
+
await fs.writeFile(tsFile, 'export const x = 1', 'utf8');
|
|
146
|
+
try {
|
|
147
|
+
const nextResolve = vi.fn();
|
|
148
|
+
const result = await poolResolve('./sibling.js', { parentURL: pathToFileURL(parentFile).href }, nextResolve);
|
|
149
|
+
expect(nextResolve).not.toHaveBeenCalled();
|
|
150
|
+
expect(result.shortCircuit).toBe(true);
|
|
151
|
+
expect(result.url).toBe(pathToFileURL(tsFile).href);
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
it('initialise sets debug mode', () => {
|
|
158
|
+
// Should not throw
|
|
159
|
+
initialise({ debug: true });
|
|
160
|
+
initialise({ debug: false });
|
|
161
|
+
initialise(undefined);
|
|
162
|
+
});
|
|
163
|
+
it('returns null from tryResolveTsExtension when parentURL is invalid', async () => {
|
|
164
|
+
const nextResolve = vi.fn().mockResolvedValue({
|
|
165
|
+
url: 'file:///fallback.js',
|
|
166
|
+
shortCircuit: false,
|
|
167
|
+
});
|
|
168
|
+
// parentURL is not a valid file URL, so tryResolveTsExtension should return null
|
|
169
|
+
await poolResolve('./foo.js', { parentURL: 'not-a-valid-url' }, nextResolve);
|
|
170
|
+
// Falls through to nextResolve
|
|
171
|
+
expect(nextResolve).toHaveBeenCalled();
|
|
172
|
+
});
|
|
91
173
|
});
|
|
@@ -55,6 +55,90 @@ describe('VitestWorkerRuntime', () => {
|
|
|
55
55
|
expect(fs.existsSync(path.join(tmp, 'src', '__mutineer__'))).toBe(false);
|
|
56
56
|
await runtime.shutdown();
|
|
57
57
|
});
|
|
58
|
+
it('throws when run is called before init', async () => {
|
|
59
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mutineer-worker-'));
|
|
60
|
+
tmpFiles.push(tmp);
|
|
61
|
+
const runtime = createVitestWorkerRuntime({
|
|
62
|
+
workerId: 'w-noinit',
|
|
63
|
+
cwd: tmp,
|
|
64
|
+
});
|
|
65
|
+
await expect(runtime.run({
|
|
66
|
+
id: 'mut#err',
|
|
67
|
+
name: 'm',
|
|
68
|
+
file: path.join(tmp, 'src.ts'),
|
|
69
|
+
code: 'export const x=1',
|
|
70
|
+
line: 1,
|
|
71
|
+
col: 1,
|
|
72
|
+
}, [path.join(tmp, 'test.ts')])).rejects.toThrow('Vitest runtime not initialised');
|
|
73
|
+
});
|
|
74
|
+
it('shutdown is a no-op when not initialised', async () => {
|
|
75
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mutineer-worker-'));
|
|
76
|
+
tmpFiles.push(tmp);
|
|
77
|
+
const runtime = createVitestWorkerRuntime({
|
|
78
|
+
workerId: 'w-noinit2',
|
|
79
|
+
cwd: tmp,
|
|
80
|
+
});
|
|
81
|
+
// Should not throw
|
|
82
|
+
await runtime.shutdown();
|
|
83
|
+
expect(closeFn).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
it('passes vitestConfigPath option to createVitest', async () => {
|
|
86
|
+
const { createVitest } = await import('vitest/node');
|
|
87
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mutineer-worker-'));
|
|
88
|
+
tmpFiles.push(tmp);
|
|
89
|
+
const runtime = createVitestWorkerRuntime({
|
|
90
|
+
workerId: 'w-config',
|
|
91
|
+
cwd: tmp,
|
|
92
|
+
vitestConfigPath: '/custom/vitest.config.ts',
|
|
93
|
+
});
|
|
94
|
+
await runtime.init();
|
|
95
|
+
expect(createVitest).toHaveBeenCalledWith('test', expect.objectContaining({ config: '/custom/vitest.config.ts' }), expect.any(Object));
|
|
96
|
+
await runtime.shutdown();
|
|
97
|
+
});
|
|
98
|
+
it('handles non-Error thrown during run', async () => {
|
|
99
|
+
runSpecsFn.mockRejectedValue('string error');
|
|
100
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mutineer-worker-'));
|
|
101
|
+
tmpFiles.push(tmp);
|
|
102
|
+
const runtime = createVitestWorkerRuntime({
|
|
103
|
+
workerId: 'w-strerr',
|
|
104
|
+
cwd: tmp,
|
|
105
|
+
});
|
|
106
|
+
await runtime.init();
|
|
107
|
+
const result = await runtime.run({
|
|
108
|
+
id: 'mut#3',
|
|
109
|
+
name: 'm',
|
|
110
|
+
file: path.join(tmp, 'src.ts'),
|
|
111
|
+
code: 'export const x=1',
|
|
112
|
+
line: 1,
|
|
113
|
+
col: 1,
|
|
114
|
+
}, [path.join(tmp, 'test.ts')]);
|
|
115
|
+
expect(result.killed).toBe(true);
|
|
116
|
+
expect(result.error).toBe('string error');
|
|
117
|
+
await runtime.shutdown();
|
|
118
|
+
});
|
|
119
|
+
it('falls back to all testModules when no relevant modules match', async () => {
|
|
120
|
+
runSpecsFn.mockResolvedValue({
|
|
121
|
+
testModules: [{ moduleId: 'unknown-module', ok: () => true }],
|
|
122
|
+
});
|
|
123
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mutineer-worker-'));
|
|
124
|
+
tmpFiles.push(tmp);
|
|
125
|
+
const runtime = createVitestWorkerRuntime({
|
|
126
|
+
workerId: 'w-fallback',
|
|
127
|
+
cwd: tmp,
|
|
128
|
+
});
|
|
129
|
+
await runtime.init();
|
|
130
|
+
const result = await runtime.run({
|
|
131
|
+
id: 'mut#4',
|
|
132
|
+
name: 'm',
|
|
133
|
+
file: path.join(tmp, 'src.ts'),
|
|
134
|
+
code: 'export const x=1',
|
|
135
|
+
line: 1,
|
|
136
|
+
col: 1,
|
|
137
|
+
}, [path.join(tmp, 'test.ts')]);
|
|
138
|
+
// Falls back to all testModules; 'unknown-module' reports ok() = true
|
|
139
|
+
expect(result.killed).toBe(false);
|
|
140
|
+
await runtime.shutdown();
|
|
141
|
+
});
|
|
58
142
|
it('returns escaped when no specs produced', async () => {
|
|
59
143
|
getProjectByNameFn.mockReturnValue({ createSpecification: () => null });
|
|
60
144
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mutineer-worker-'));
|
|
@@ -154,7 +154,7 @@ export class VitestAdapter {
|
|
|
154
154
|
}
|
|
155
155
|
async runMutant(mutant, tests) {
|
|
156
156
|
if (!this.pool) {
|
|
157
|
-
throw new Error('VitestAdapter not
|
|
157
|
+
throw new Error('VitestAdapter not initialised. Call init() first.');
|
|
158
158
|
}
|
|
159
159
|
try {
|
|
160
160
|
const result = await this.pool.run(mutant, [...tests]);
|
|
@@ -7,5 +7,4 @@
|
|
|
7
7
|
export { VitestAdapter, createVitestAdapter, isCoverageRequestedInArgs, } from './adapter.js';
|
|
8
8
|
export { VitestPool, runWithPool, type VitestPoolOptions } from './pool.js';
|
|
9
9
|
export { poolMutineerPlugin } from './plugin.js';
|
|
10
|
-
export { resolve as poolRedirectResolve } from './redirect-loader.js';
|
|
11
10
|
export type { MutantPayload, MutantRunResult, MutantRunSummary, } from '../../types/mutant.js';
|
|
@@ -7,4 +7,3 @@
|
|
|
7
7
|
export { VitestAdapter, createVitestAdapter, isCoverageRequestedInArgs, } from './adapter.js';
|
|
8
8
|
export { VitestPool, runWithPool } from './pool.js';
|
|
9
9
|
export { poolMutineerPlugin } from './plugin.js';
|
|
10
|
-
export { resolve as poolRedirectResolve } from './redirect-loader.js';
|
|
@@ -221,7 +221,7 @@ export class VitestPool {
|
|
|
221
221
|
this.workers = [];
|
|
222
222
|
this.availableWorkers = [];
|
|
223
223
|
this.waitingTasks = [];
|
|
224
|
-
this.
|
|
224
|
+
this.initialised = false;
|
|
225
225
|
this.shuttingDown = false;
|
|
226
226
|
this.options = {
|
|
227
227
|
cwd: options.cwd,
|
|
@@ -232,7 +232,7 @@ export class VitestPool {
|
|
|
232
232
|
};
|
|
233
233
|
}
|
|
234
234
|
async init() {
|
|
235
|
-
if (this.
|
|
235
|
+
if (this.initialised)
|
|
236
236
|
return;
|
|
237
237
|
poolLog.debug(`Initializing pool with ${this.options.concurrency} workers`);
|
|
238
238
|
const startPromises = [];
|
|
@@ -254,8 +254,8 @@ export class VitestPool {
|
|
|
254
254
|
}));
|
|
255
255
|
}
|
|
256
256
|
await Promise.all(startPromises);
|
|
257
|
-
this.
|
|
258
|
-
poolLog.debug('Pool
|
|
257
|
+
this.initialised = true;
|
|
258
|
+
poolLog.debug('Pool initialised');
|
|
259
259
|
}
|
|
260
260
|
handleWorkerExit(worker) {
|
|
261
261
|
// Remove from available list
|
|
@@ -313,8 +313,8 @@ export class VitestPool {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
async run(mutant, tests) {
|
|
316
|
-
if (!this.
|
|
317
|
-
throw new Error('Pool not
|
|
316
|
+
if (!this.initialised) {
|
|
317
|
+
throw new Error('Pool not initialised. Call init() first.');
|
|
318
318
|
}
|
|
319
319
|
if (this.shuttingDown) {
|
|
320
320
|
throw new Error('Pool is shutting down');
|
|
@@ -336,7 +336,7 @@ export class VitestPool {
|
|
|
336
336
|
await Promise.all(this.workers.map((w) => w.shutdown()));
|
|
337
337
|
this.workers = [];
|
|
338
338
|
this.availableWorkers = [];
|
|
339
|
-
this.
|
|
339
|
+
this.initialised = false;
|
|
340
340
|
poolLog.debug('Pool shut down');
|
|
341
341
|
}
|
|
342
342
|
}
|
|
@@ -13,7 +13,7 @@ declare global {
|
|
|
13
13
|
to: string | null;
|
|
14
14
|
} | undefined;
|
|
15
15
|
}
|
|
16
|
-
export declare function
|
|
16
|
+
export declare function initialise(data: {
|
|
17
17
|
debug?: boolean;
|
|
18
18
|
} | undefined): void;
|
|
19
19
|
export declare function resolve(specifier: string, context: {
|
|
@@ -17,7 +17,7 @@ register(import.meta.url, {
|
|
|
17
17
|
data: { debug: process.env.MUTINEER_DEBUG === '1' },
|
|
18
18
|
});
|
|
19
19
|
let DEBUG = process.env.MUTINEER_DEBUG === '1';
|
|
20
|
-
export function
|
|
20
|
+
export function initialise(data) {
|
|
21
21
|
if (data?.debug !== undefined) {
|
|
22
22
|
DEBUG = data.debug;
|
|
23
23
|
}
|
|
@@ -25,10 +25,10 @@ export class VitestWorkerRuntime {
|
|
|
25
25
|
plugins: [poolMutineerPlugin()],
|
|
26
26
|
});
|
|
27
27
|
await this.vitest.init();
|
|
28
|
-
log.debug(`Vitest
|
|
28
|
+
log.debug(`Vitest initialised for worker ${this.options.workerId}`);
|
|
29
29
|
}
|
|
30
30
|
catch (err) {
|
|
31
|
-
log.error(`Failed to
|
|
31
|
+
log.error(`Failed to initialise Vitest: ${err}`);
|
|
32
32
|
throw err;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -40,7 +40,7 @@ export class VitestWorkerRuntime {
|
|
|
40
40
|
}
|
|
41
41
|
async run(mutant, tests) {
|
|
42
42
|
if (!this.vitest) {
|
|
43
|
-
throw new Error('Vitest runtime not
|
|
43
|
+
throw new Error('Vitest runtime not initialised');
|
|
44
44
|
}
|
|
45
45
|
const start = Date.now();
|
|
46
46
|
try {
|
package/dist/types/mutant.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface Variant extends MutantDescriptor {
|
|
|
25
25
|
export interface MutantCacheEntry extends MutantLocation {
|
|
26
26
|
readonly status: MutantStatus;
|
|
27
27
|
readonly mutator: string;
|
|
28
|
+
readonly originalSnippet?: string;
|
|
29
|
+
readonly mutatedSnippet?: string;
|
|
28
30
|
}
|
|
29
31
|
export interface MutantResult extends MutantCacheEntry {
|
|
30
32
|
readonly id: string;
|