@mutineerjs/mutineer 0.2.3 → 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/core/__tests__/module.spec.js +66 -3
- package/dist/core/__tests__/sfc.spec.js +76 -0
- package/dist/core/__tests__/variant-utils.spec.js +93 -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__/args.spec.js +225 -0
- package/dist/runner/__tests__/cache.spec.js +180 -0
- package/dist/runner/__tests__/changed.spec.js +227 -0
- package/dist/runner/__tests__/cleanup.spec.js +41 -0
- package/dist/runner/__tests__/config.spec.js +71 -0
- package/dist/runner/__tests__/coverage-resolver.spec.js +171 -0
- package/dist/runner/__tests__/pool-executor.spec.js +211 -0
- package/dist/runner/__tests__/tasks.spec.js +95 -0
- package/dist/runner/__tests__/variants.spec.js +261 -0
- package/dist/runner/args.d.ts +5 -0
- package/dist/runner/args.js +7 -0
- package/dist/runner/config.js +2 -2
- package/dist/runner/coverage-resolver.d.ts +21 -0
- package/dist/runner/coverage-resolver.js +96 -0
- package/dist/runner/discover.js +2 -1
- package/dist/runner/jest/__tests__/adapter.spec.js +1 -1
- package/dist/runner/jest/__tests__/pool.spec.d.ts +1 -0
- package/dist/runner/jest/__tests__/pool.spec.js +211 -0
- package/dist/runner/jest/__tests__/worker-runtime.spec.d.ts +1 -0
- package/dist/runner/jest/__tests__/worker-runtime.spec.js +148 -0
- 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/orchestrator.js +43 -295
- package/dist/runner/pool-executor.d.ts +17 -0
- package/dist/runner/pool-executor.js +143 -0
- package/dist/runner/shared/__tests__/mutant-paths.spec.d.ts +1 -0
- package/dist/runner/shared/__tests__/mutant-paths.spec.js +66 -0
- package/dist/runner/shared/__tests__/redirect-state.spec.d.ts +1 -0
- package/dist/runner/shared/__tests__/redirect-state.spec.js +56 -0
- 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/tasks.d.ts +12 -0
- package/dist/runner/tasks.js +25 -0
- package/dist/runner/types.d.ts +1 -1
- package/dist/runner/variants.d.ts +17 -2
- package/dist/runner/variants.js +33 -0
- 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 +87 -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__/logger.spec.d.ts +1 -0
- package/dist/utils/__tests__/logger.spec.js +61 -0
- package/dist/utils/__tests__/normalizePath.spec.d.ts +1 -0
- package/dist/utils/__tests__/normalizePath.spec.js +22 -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.js +0 -59
- 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.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.js +0 -118
- 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.js +0 -83
- package/dist/runner/pool/__tests__/pool-plugin.spec.js +0 -59
- 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.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.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/{runner/__tests__/orchestrator.spec.d.ts → core/__tests__/sfc.spec.d.ts} +0 -0
- /package/dist/{runner/adapters/__tests__/jest.spec.d.ts → core/__tests__/variant-utils.spec.d.ts} +0 -0
- /package/dist/{runner/adapters/__tests__/vitest-worker-runtime.spec.d.ts → mutators/__tests__/operator.spec.d.ts} +0 -0
- /package/dist/{runner/adapters/__tests__/vitest.spec.d.ts → mutators/__tests__/return-value.spec.d.ts} +0 -0
- /package/dist/runner/{adapters/jest/__tests__/index.spec.d.ts → __tests__/args.spec.d.ts} +0 -0
- /package/dist/runner/{adapters/vitest/__tests__/index.spec.d.ts → __tests__/cache.spec.d.ts} +0 -0
- /package/dist/runner/{adapters/vitest/__tests__/worker-runtime.spec.d.ts → __tests__/changed.spec.d.ts} +0 -0
- /package/dist/runner/{pool/__tests__/index.spec.d.ts → __tests__/cleanup.spec.d.ts} +0 -0
- /package/dist/runner/{pool/__tests__/pool-plugin.spec.d.ts → __tests__/config.spec.d.ts} +0 -0
- /package/dist/runner/{pool/__tests__/pool-redirect-loader.spec.d.ts → __tests__/coverage-resolver.spec.d.ts} +0 -0
- /package/dist/runner/{pool/jest-worker.d.mts → __tests__/pool-executor.spec.d.ts} +0 -0
- /package/dist/runner/{pool/jest/worker.d.mts → __tests__/tasks.spec.d.ts} +0 -0
- /package/dist/{types/api.js → runner/__tests__/variants.spec.d.ts} +0 -0
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import { poolMutineerPlugin } from '../plugin.js';
|
|
6
|
-
function getLoadFn() {
|
|
7
|
-
const plugin = poolMutineerPlugin();
|
|
8
|
-
if (Array.isArray(plugin)) {
|
|
9
|
-
return plugin[0]?.load;
|
|
10
|
-
}
|
|
11
|
-
if (plugin && typeof plugin === 'object' && 'load' in plugin) {
|
|
12
|
-
return plugin.load;
|
|
13
|
-
}
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
describe('poolMutineerPlugin', () => {
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
;
|
|
19
|
-
globalThis.__mutineer_redirect__ = undefined;
|
|
20
|
-
});
|
|
21
|
-
it('returns null when no redirect is configured', () => {
|
|
22
|
-
const load = getLoadFn();
|
|
23
|
-
const result = load?.('/some/file.ts');
|
|
24
|
-
expect(result).toBeNull();
|
|
25
|
-
});
|
|
26
|
-
it('returns the mutated file when the id matches the redirect target', async () => {
|
|
27
|
-
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-pool-plugin-'));
|
|
28
|
-
const fromPath = path.join(tmpDir, 'source.ts');
|
|
29
|
-
const mutatedPath = path.join(tmpDir, 'mutated.ts');
|
|
30
|
-
await fs.writeFile(fromPath, 'export const original = true\n', 'utf8');
|
|
31
|
-
await fs.writeFile(mutatedPath, 'export const mutated = true\n', 'utf8');
|
|
32
|
-
try {
|
|
33
|
-
;
|
|
34
|
-
globalThis.__mutineer_redirect__ = { from: fromPath, to: mutatedPath };
|
|
35
|
-
const load = getLoadFn();
|
|
36
|
-
const result = load?.(fromPath);
|
|
37
|
-
expect(result).toBe('export const mutated = true\n');
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
it('ignores modules that do not match the redirect', async () => {
|
|
44
|
-
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-pool-plugin-'));
|
|
45
|
-
const fromPath = path.join(tmpDir, 'source.ts');
|
|
46
|
-
const mutatedPath = path.join(tmpDir, 'mutated.ts');
|
|
47
|
-
await fs.writeFile(mutatedPath, 'export const mutated = true\n', 'utf8');
|
|
48
|
-
try {
|
|
49
|
-
;
|
|
50
|
-
globalThis.__mutineer_redirect__ = { from: fromPath, to: mutatedPath };
|
|
51
|
-
const load = getLoadFn();
|
|
52
|
-
const result = load?.(path.join(tmpDir, 'other.ts?import'));
|
|
53
|
-
expect(result).toBeNull();
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
});
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import { pathToFileURL } from 'node:url';
|
|
6
|
-
import { resolve as poolResolve } from '../redirect-loader.js';
|
|
7
|
-
describe('pool-redirect-loader resolve', () => {
|
|
8
|
-
afterEach(() => {
|
|
9
|
-
;
|
|
10
|
-
globalThis.__mutineer_redirect__ = undefined;
|
|
11
|
-
vi.restoreAllMocks();
|
|
12
|
-
});
|
|
13
|
-
it('resolves .js to .ts in the same directory', async () => {
|
|
14
|
-
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-pool-loader-'));
|
|
15
|
-
const parentFile = path.join(tmpDir, 'src', 'index.ts');
|
|
16
|
-
const tsFile = path.join(tmpDir, 'src', 'foo.ts');
|
|
17
|
-
await fs.mkdir(path.dirname(parentFile), { recursive: true });
|
|
18
|
-
await fs.writeFile(parentFile, 'export {}', 'utf8');
|
|
19
|
-
await fs.writeFile(tsFile, 'export const foo = 1', 'utf8');
|
|
20
|
-
try {
|
|
21
|
-
const nextResolve = vi.fn();
|
|
22
|
-
const result = await poolResolve('./foo.js', { parentURL: pathToFileURL(parentFile).href }, nextResolve);
|
|
23
|
-
expect(nextResolve).not.toHaveBeenCalled();
|
|
24
|
-
expect(result.shortCircuit).toBe(true);
|
|
25
|
-
expect(result.url).toBe(pathToFileURL(tsFile).href);
|
|
26
|
-
}
|
|
27
|
-
finally {
|
|
28
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
it('redirects to mutated file when target matches', async () => {
|
|
32
|
-
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-pool-loader-'));
|
|
33
|
-
const parentFile = path.join(tmpDir, 'src', 'index.ts');
|
|
34
|
-
const fromPath = path.join(tmpDir, 'src', 'target.ts');
|
|
35
|
-
const mutatedPath = path.join(tmpDir, 'mutated.ts');
|
|
36
|
-
await fs.mkdir(path.dirname(parentFile), { recursive: true });
|
|
37
|
-
await fs.writeFile(parentFile, 'export {}', 'utf8');
|
|
38
|
-
await fs.writeFile(fromPath, 'export const target = true', 'utf8');
|
|
39
|
-
await fs.writeFile(mutatedPath, 'export const mutated = true', 'utf8');
|
|
40
|
-
try {
|
|
41
|
-
;
|
|
42
|
-
globalThis.__mutineer_redirect__ = { from: fromPath, to: mutatedPath };
|
|
43
|
-
const nextResolve = vi.fn();
|
|
44
|
-
const result = await poolResolve('./target.js', { parentURL: pathToFileURL(parentFile).href }, nextResolve);
|
|
45
|
-
expect(nextResolve).not.toHaveBeenCalled();
|
|
46
|
-
expect(result.shortCircuit).toBe(true);
|
|
47
|
-
expect(result.url).toBe(pathToFileURL(mutatedPath).href);
|
|
48
|
-
}
|
|
49
|
-
finally {
|
|
50
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
it('redirects after delegated resolution', async () => {
|
|
54
|
-
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-pool-loader-'));
|
|
55
|
-
const parentFile = path.join(tmpDir, 'src', 'index.ts');
|
|
56
|
-
const fromPath = path.join(tmpDir, 'src', 'delegated.ts');
|
|
57
|
-
const mutatedPath = path.join(tmpDir, 'mutated.ts');
|
|
58
|
-
await fs.mkdir(path.dirname(parentFile), { recursive: true });
|
|
59
|
-
await fs.writeFile(parentFile, 'export {}', 'utf8');
|
|
60
|
-
await fs.writeFile(fromPath, 'export const delegated = true', 'utf8');
|
|
61
|
-
await fs.writeFile(mutatedPath, 'export const mutated = true', 'utf8');
|
|
62
|
-
try {
|
|
63
|
-
;
|
|
64
|
-
globalThis.__mutineer_redirect__ = { from: fromPath, to: mutatedPath };
|
|
65
|
-
const nextResolve = vi.fn().mockResolvedValue({
|
|
66
|
-
url: pathToFileURL(fromPath).href,
|
|
67
|
-
shortCircuit: false
|
|
68
|
-
});
|
|
69
|
-
const result = await poolResolve('./delegated', { parentURL: pathToFileURL(parentFile).href }, nextResolve);
|
|
70
|
-
expect(nextResolve).toHaveBeenCalledOnce();
|
|
71
|
-
expect(result.shortCircuit).toBe(true);
|
|
72
|
-
expect(result.url).toBe(pathToFileURL(mutatedPath).href);
|
|
73
|
-
}
|
|
74
|
-
finally {
|
|
75
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
});
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test Runner Worker Pools
|
|
3
|
-
*
|
|
4
|
-
* Exports worker pool implementations for different test runners.
|
|
5
|
-
*/
|
|
6
|
-
export { VitestPool, runWithPool, type VitestPoolOptions } from './vitest/pool.js';
|
|
7
|
-
export { JestPool, runWithJestPool, type JestPoolOptions } from './jest/pool.js';
|
|
8
|
-
export type { MutantPayload, MutantRunResult, MutantRunSummary } from '../../types/mutant.js';
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test Runner Worker Pools
|
|
3
|
-
*
|
|
4
|
-
* Exports worker pool implementations for different test runners.
|
|
5
|
-
*/
|
|
6
|
-
// Vitest pool
|
|
7
|
-
export { VitestPool, runWithPool } from './vitest/pool.js';
|
|
8
|
-
// Jest pool
|
|
9
|
-
export { JestPool, runWithJestPool } from './jest/pool.js';
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'node:events';
|
|
2
|
-
import type { MutantPayload, MutantRunResult, MutantRunSummary } from '../../../types/mutant.js';
|
|
3
|
-
declare class JestWorker extends EventEmitter {
|
|
4
|
-
private readonly cwd;
|
|
5
|
-
private readonly jestConfig?;
|
|
6
|
-
private readonly debug;
|
|
7
|
-
readonly id: string;
|
|
8
|
-
private process;
|
|
9
|
-
private pendingTask;
|
|
10
|
-
private ready;
|
|
11
|
-
private shuttingDown;
|
|
12
|
-
constructor(id: string, cwd: string, jestConfig?: string | undefined, debug?: boolean);
|
|
13
|
-
start(): Promise<void>;
|
|
14
|
-
private handleMessage;
|
|
15
|
-
private handleExit;
|
|
16
|
-
isReady(): boolean;
|
|
17
|
-
isBusy(): boolean;
|
|
18
|
-
run(mutant: MutantPayload, tests: string[], timeoutMs?: number): Promise<MutantRunSummary>;
|
|
19
|
-
shutdown(): Promise<void>;
|
|
20
|
-
kill(): void;
|
|
21
|
-
private log;
|
|
22
|
-
}
|
|
23
|
-
export interface JestPoolOptions {
|
|
24
|
-
cwd: string;
|
|
25
|
-
concurrency: number;
|
|
26
|
-
jestConfig?: string;
|
|
27
|
-
timeoutMs?: number;
|
|
28
|
-
debug?: boolean;
|
|
29
|
-
createWorker?: (id: string, opts: {
|
|
30
|
-
cwd: string;
|
|
31
|
-
jestConfig?: string;
|
|
32
|
-
debug: boolean;
|
|
33
|
-
}) => JestWorker;
|
|
34
|
-
}
|
|
35
|
-
export declare class JestPool {
|
|
36
|
-
private workers;
|
|
37
|
-
private availableWorkers;
|
|
38
|
-
private waitingTasks;
|
|
39
|
-
private readonly options;
|
|
40
|
-
private initialized;
|
|
41
|
-
private shuttingDown;
|
|
42
|
-
constructor(options: JestPoolOptions);
|
|
43
|
-
init(): Promise<void>;
|
|
44
|
-
private handleWorkerExit;
|
|
45
|
-
private acquireWorker;
|
|
46
|
-
private releaseWorker;
|
|
47
|
-
run(mutant: MutantPayload, tests: string[]): Promise<MutantRunSummary>;
|
|
48
|
-
shutdown(): Promise<void>;
|
|
49
|
-
private log;
|
|
50
|
-
}
|
|
51
|
-
export declare function runWithJestPool(pool: JestPool, mutant: MutantPayload, tests: readonly string[]): Promise<MutantRunResult>;
|
|
52
|
-
export type { MutantPayload, MutantRunResult, MutantRunSummary } from '../../../types/mutant.js';
|
|
@@ -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, 'worker.js');
|
|
21
|
-
const workerMts = path.join(__dirname, 'worker.mjs');
|
|
22
|
-
const workerTs = path.join(__dirname, '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,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,52 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'node:events';
|
|
2
|
-
import type { MutantPayload, MutantRunResult, MutantRunSummary } from '../../types/mutant.js';
|
|
3
|
-
declare class JestWorker extends EventEmitter {
|
|
4
|
-
private readonly cwd;
|
|
5
|
-
private readonly jestConfig?;
|
|
6
|
-
private readonly debug;
|
|
7
|
-
readonly id: string;
|
|
8
|
-
private process;
|
|
9
|
-
private pendingTask;
|
|
10
|
-
private ready;
|
|
11
|
-
private shuttingDown;
|
|
12
|
-
constructor(id: string, cwd: string, jestConfig?: string | undefined, debug?: boolean);
|
|
13
|
-
start(): Promise<void>;
|
|
14
|
-
private handleMessage;
|
|
15
|
-
private handleExit;
|
|
16
|
-
isReady(): boolean;
|
|
17
|
-
isBusy(): boolean;
|
|
18
|
-
run(mutant: MutantPayload, tests: string[], timeoutMs?: number): Promise<MutantRunSummary>;
|
|
19
|
-
shutdown(): Promise<void>;
|
|
20
|
-
kill(): void;
|
|
21
|
-
private log;
|
|
22
|
-
}
|
|
23
|
-
export interface JestPoolOptions {
|
|
24
|
-
cwd: string;
|
|
25
|
-
concurrency: number;
|
|
26
|
-
jestConfig?: string;
|
|
27
|
-
timeoutMs?: number;
|
|
28
|
-
debug?: boolean;
|
|
29
|
-
createWorker?: (id: string, opts: {
|
|
30
|
-
cwd: string;
|
|
31
|
-
jestConfig?: string;
|
|
32
|
-
debug: boolean;
|
|
33
|
-
}) => JestWorker;
|
|
34
|
-
}
|
|
35
|
-
export declare class JestPool {
|
|
36
|
-
private workers;
|
|
37
|
-
private availableWorkers;
|
|
38
|
-
private waitingTasks;
|
|
39
|
-
private readonly options;
|
|
40
|
-
private initialized;
|
|
41
|
-
private shuttingDown;
|
|
42
|
-
constructor(options: JestPoolOptions);
|
|
43
|
-
init(): Promise<void>;
|
|
44
|
-
private handleWorkerExit;
|
|
45
|
-
private acquireWorker;
|
|
46
|
-
private releaseWorker;
|
|
47
|
-
run(mutant: MutantPayload, tests: string[]): Promise<MutantRunSummary>;
|
|
48
|
-
shutdown(): Promise<void>;
|
|
49
|
-
private log;
|
|
50
|
-
}
|
|
51
|
-
export declare function runWithJestPool(pool: JestPool, mutant: MutantPayload, tests: readonly string[]): Promise<MutantRunResult>;
|
|
52
|
-
export type { MutantPayload, MutantRunResult, MutantRunSummary } from '../../types/mutant.js';
|