@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,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test Runner Adapter Interface
|
|
3
|
-
*
|
|
4
|
-
* This module defines the interface that all test runner adapters must implement.
|
|
5
|
-
* Adapters abstract test runner-specific details (Vitest, Jest, etc.) from the
|
|
6
|
-
* mutation testing orchestrator.
|
|
7
|
-
*/
|
|
8
|
-
import type { MutineerConfig } from '../../types/config.js';
|
|
9
|
-
import type { MutantPayload, MutantRunResult } from '../../types/mutant.js';
|
|
10
|
-
/**
|
|
11
|
-
* Options for initializing a test runner adapter.
|
|
12
|
-
*/
|
|
13
|
-
export interface TestRunnerAdapterOptions {
|
|
14
|
-
readonly cwd: string;
|
|
15
|
-
readonly concurrency: number;
|
|
16
|
-
readonly timeoutMs: number;
|
|
17
|
-
readonly debug: boolean;
|
|
18
|
-
readonly config: MutineerConfig;
|
|
19
|
-
readonly cliArgs: string[];
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Coverage-related configuration detected from the test runner.
|
|
23
|
-
*/
|
|
24
|
-
export interface CoverageConfig {
|
|
25
|
-
/** Whether per-test coverage is enabled in the config */
|
|
26
|
-
readonly perTestEnabled: boolean;
|
|
27
|
-
/** Whether coverage is enabled in the config */
|
|
28
|
-
readonly coverageEnabled: boolean;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Interface that all test runner adapters must implement.
|
|
32
|
-
*
|
|
33
|
-
* A test runner adapter handles:
|
|
34
|
-
* - Running baseline tests before mutation testing
|
|
35
|
-
* - Running mutant tests (with code substitution)
|
|
36
|
-
* - Managing worker pools for parallel execution
|
|
37
|
-
* - Detecting coverage configuration
|
|
38
|
-
*/
|
|
39
|
-
export interface TestRunnerAdapter {
|
|
40
|
-
/**
|
|
41
|
-
* The name of the test runner (e.g., 'vitest', 'jest').
|
|
42
|
-
*/
|
|
43
|
-
readonly name: string;
|
|
44
|
-
/**
|
|
45
|
-
* Initialize the adapter (start worker pools, etc.).
|
|
46
|
-
* Must be called before running tests.
|
|
47
|
-
*/
|
|
48
|
-
init(concurrencyOverride?: number): Promise<void>;
|
|
49
|
-
/**
|
|
50
|
-
* Run baseline tests to ensure they pass before mutation testing.
|
|
51
|
-
* @param tests - Array of test file paths to run
|
|
52
|
-
* @param options - Options for the baseline run
|
|
53
|
-
* @returns true if all tests pass, false otherwise
|
|
54
|
-
*/
|
|
55
|
-
runBaseline(tests: readonly string[], options: BaselineOptions): Promise<boolean>;
|
|
56
|
-
/**
|
|
57
|
-
* Run a single mutant against its associated tests.
|
|
58
|
-
* @param mutant - The mutation to test
|
|
59
|
-
* @param tests - Array of test file paths to run
|
|
60
|
-
* @returns Result indicating if the mutant was killed, escaped, or errored
|
|
61
|
-
*/
|
|
62
|
-
runMutant(mutant: MutantPayload, tests: readonly string[]): Promise<MutantRunResult>;
|
|
63
|
-
/**
|
|
64
|
-
* Shutdown the adapter (stop worker pools, cleanup, etc.).
|
|
65
|
-
*/
|
|
66
|
-
shutdown(): Promise<void>;
|
|
67
|
-
/**
|
|
68
|
-
* Check if the coverage provider is installed.
|
|
69
|
-
*/
|
|
70
|
-
hasCoverageProvider(): boolean;
|
|
71
|
-
/**
|
|
72
|
-
* Detect coverage configuration from the test runner config.
|
|
73
|
-
*/
|
|
74
|
-
detectCoverageConfig(): Promise<CoverageConfig>;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Options for running baseline tests.
|
|
78
|
-
*/
|
|
79
|
-
export interface BaselineOptions {
|
|
80
|
-
/** Whether to collect coverage during baseline run */
|
|
81
|
-
readonly collectCoverage: boolean;
|
|
82
|
-
/** Whether to collect per-test coverage */
|
|
83
|
-
readonly perTestCoverage: boolean;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Factory function type for creating test runner adapters.
|
|
87
|
-
*/
|
|
88
|
-
export type TestRunnerAdapterFactory = (options: TestRunnerAdapterOptions) => TestRunnerAdapter;
|
|
89
|
-
export type { MutantPayload, MutantRunResult, MutantRunStatus } from '../../types/mutant.js';
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import fs from 'node:fs/promises';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import { createVitestAdapter, isCoverageRequestedInArgs } from '../index.js';
|
|
6
|
-
// Mock VitestPool to avoid spinning processes
|
|
7
|
-
var poolInstance = null;
|
|
8
|
-
var poolCtorOpts = null;
|
|
9
|
-
vi.mock('../../../pool/index.js', () => {
|
|
10
|
-
class MockPool {
|
|
11
|
-
constructor(opts) {
|
|
12
|
-
this.init = vi.fn();
|
|
13
|
-
this.run = vi.fn();
|
|
14
|
-
this.shutdown = vi.fn();
|
|
15
|
-
poolCtorOpts = opts;
|
|
16
|
-
poolInstance = this;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return { VitestPool: MockPool };
|
|
20
|
-
});
|
|
21
|
-
// Mock spawn for baseline runs
|
|
22
|
-
var spawnMock;
|
|
23
|
-
vi.mock('node:child_process', async () => {
|
|
24
|
-
const actual = await vi.importActual('node:child_process');
|
|
25
|
-
spawnMock = vi.fn();
|
|
26
|
-
return { ...actual, spawn: spawnMock };
|
|
27
|
-
});
|
|
28
|
-
function makeAdapter(opts = {}) {
|
|
29
|
-
return createVitestAdapter({
|
|
30
|
-
cwd: opts.cwd ?? process.cwd(),
|
|
31
|
-
concurrency: opts.concurrency ?? 2,
|
|
32
|
-
timeoutMs: opts.timeoutMs ?? 1000,
|
|
33
|
-
debug: opts.debug ?? false,
|
|
34
|
-
config: opts.config ?? { vitestConfig: undefined },
|
|
35
|
-
cliArgs: opts.cliArgs ?? ['--changed']
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
describe('Vitest adapter', () => {
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
vi.clearAllMocks();
|
|
41
|
-
});
|
|
42
|
-
afterEach(() => {
|
|
43
|
-
vi.useRealTimers();
|
|
44
|
-
});
|
|
45
|
-
it('initializes pool with override concurrency', async () => {
|
|
46
|
-
const adapter = makeAdapter();
|
|
47
|
-
await adapter.init(5);
|
|
48
|
-
expect(poolInstance?.init).toHaveBeenCalledTimes(1);
|
|
49
|
-
expect(poolCtorOpts.concurrency).toBe(5);
|
|
50
|
-
});
|
|
51
|
-
it('maps pool result to mutant status', async () => {
|
|
52
|
-
poolInstance = null;
|
|
53
|
-
poolCtorOpts = null;
|
|
54
|
-
const adapter = makeAdapter();
|
|
55
|
-
await adapter.init();
|
|
56
|
-
poolInstance.run.mockResolvedValueOnce({ killed: true, durationMs: 10 });
|
|
57
|
-
const res = await adapter.runMutant({ id: '1', name: 'm', file: 'f', code: 'c', line: 1, col: 1 }, ['t']);
|
|
58
|
-
expect(res).toEqual({ status: 'killed', durationMs: 10, error: undefined });
|
|
59
|
-
});
|
|
60
|
-
it('maps pool timeout errors to timeout status', async () => {
|
|
61
|
-
const adapter = makeAdapter();
|
|
62
|
-
await adapter.init();
|
|
63
|
-
poolInstance.run.mockResolvedValueOnce({ killed: true, durationMs: 15, error: 'timeout' });
|
|
64
|
-
const res = await adapter.runMutant({ id: '1', name: 'm', file: 'f', code: 'c', line: 1, col: 1 }, ['t']);
|
|
65
|
-
expect(res).toEqual({ status: 'timeout', durationMs: 15, error: 'timeout' });
|
|
66
|
-
});
|
|
67
|
-
it('maps pool errors to error status', async () => {
|
|
68
|
-
const adapter = makeAdapter();
|
|
69
|
-
await adapter.init();
|
|
70
|
-
poolInstance.run.mockResolvedValueOnce({ killed: true, durationMs: 12, error: 'crash' });
|
|
71
|
-
const res = await adapter.runMutant({ id: '1', name: 'm', file: 'f', code: 'c', line: 1, col: 1 }, ['t']);
|
|
72
|
-
expect(res).toEqual({ status: 'error', durationMs: 12, error: 'crash' });
|
|
73
|
-
});
|
|
74
|
-
it('returns error status on pool throw', async () => {
|
|
75
|
-
const adapter = makeAdapter();
|
|
76
|
-
await adapter.init();
|
|
77
|
-
poolInstance.run.mockRejectedValueOnce(new Error('boom'));
|
|
78
|
-
const res = await adapter.runMutant({ id: '1', name: 'm', file: 'f', code: 'c', line: 1, col: 1 }, ['t']);
|
|
79
|
-
expect(res.status).toBe('error');
|
|
80
|
-
});
|
|
81
|
-
it('includes coverage args for baseline when requested', async () => {
|
|
82
|
-
const adapter = makeAdapter({ cliArgs: [] });
|
|
83
|
-
spawnMock.mockImplementationOnce(() => ({
|
|
84
|
-
on: (evt, cb) => {
|
|
85
|
-
if (evt === 'exit')
|
|
86
|
-
cb(0);
|
|
87
|
-
}
|
|
88
|
-
}));
|
|
89
|
-
const ok = await adapter.runBaseline(['test-a'], { collectCoverage: true, perTestCoverage: true });
|
|
90
|
-
expect(ok).toBe(true);
|
|
91
|
-
const call = spawnMock.mock.calls[0];
|
|
92
|
-
const args = call[1];
|
|
93
|
-
expect(args.join(' ')).toContain('--coverage.enabled=true');
|
|
94
|
-
expect(args.join(' ')).toContain('--coverage.perTest=true');
|
|
95
|
-
});
|
|
96
|
-
it('detects coverage config from vitest config file', async () => {
|
|
97
|
-
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-vitest-'));
|
|
98
|
-
const cfgPath = path.join(tmp, 'vitest.config.ts');
|
|
99
|
-
await fs.writeFile(cfgPath, 'export default { coverage: { enabled: true }, test: { coverage: { perTest: true } } }');
|
|
100
|
-
try {
|
|
101
|
-
const adapter = makeAdapter({ cwd: tmp, config: { vitestConfig: 'vitest.config.ts' } });
|
|
102
|
-
const coverage = await adapter.detectCoverageConfig();
|
|
103
|
-
expect(coverage.coverageEnabled).toBe(true);
|
|
104
|
-
expect(coverage.perTestEnabled).toBe(true);
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
107
|
-
await fs.rm(tmp, { recursive: true, force: true });
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
describe('isCoverageRequestedInArgs', () => {
|
|
112
|
-
it('detects enabled coverage flags', () => {
|
|
113
|
-
expect(isCoverageRequestedInArgs(['--coverage'])).toBe(true);
|
|
114
|
-
expect(isCoverageRequestedInArgs(['--coverage.enabled=true'])).toBe(true);
|
|
115
|
-
expect(isCoverageRequestedInArgs(['--coverage.enabled=false'])).toBe(false);
|
|
116
|
-
expect(isCoverageRequestedInArgs(['--no-coverage'])).toBe(false);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import { createVitestWorkerRuntime } from '../worker-runtime.js';
|
|
6
|
-
const initFn = vi.fn();
|
|
7
|
-
const closeFn = vi.fn();
|
|
8
|
-
const runSpecsFn = vi.fn();
|
|
9
|
-
const invalidateFn = vi.fn();
|
|
10
|
-
const getProjectByNameFn = vi.fn();
|
|
11
|
-
vi.mock('vitest/node', () => ({
|
|
12
|
-
createVitest: vi.fn(async () => ({
|
|
13
|
-
init: initFn,
|
|
14
|
-
close: closeFn,
|
|
15
|
-
runTestSpecifications: runSpecsFn,
|
|
16
|
-
invalidateFile: invalidateFn,
|
|
17
|
-
getProjectByName: getProjectByNameFn,
|
|
18
|
-
}))
|
|
19
|
-
}));
|
|
20
|
-
describe('VitestWorkerRuntime', () => {
|
|
21
|
-
const tmpFiles = [];
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
vi.clearAllMocks();
|
|
24
|
-
getProjectByNameFn.mockReturnValue({
|
|
25
|
-
createSpecification: (file) => ({ moduleId: file })
|
|
26
|
-
});
|
|
27
|
-
runSpecsFn.mockResolvedValue({ testModules: [{ moduleId: 'a', ok: () => false }] });
|
|
28
|
-
});
|
|
29
|
-
afterEach(() => {
|
|
30
|
-
for (const f of tmpFiles.splice(0)) {
|
|
31
|
-
try {
|
|
32
|
-
fs.rmSync(f, { recursive: true, force: true });
|
|
33
|
-
}
|
|
34
|
-
catch { }
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
it('runs specs and reports kill based on results', async () => {
|
|
38
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mutineer-worker-'));
|
|
39
|
-
tmpFiles.push(tmp);
|
|
40
|
-
const runtime = createVitestWorkerRuntime({ workerId: 'w1', cwd: tmp });
|
|
41
|
-
await runtime.init();
|
|
42
|
-
const result = await runtime.run({ id: 'mut#1', name: 'm', file: path.join(tmp, 'src.ts'), code: 'export const x=1', line: 1, col: 1 }, [path.join(tmp, 'test.ts')]);
|
|
43
|
-
expect(initFn).toHaveBeenCalled();
|
|
44
|
-
expect(runSpecsFn).toHaveBeenCalled();
|
|
45
|
-
expect(result.killed).toBe(true);
|
|
46
|
-
expect(fs.existsSync(path.join(tmp, 'src', '__mutineer__'))).toBe(false);
|
|
47
|
-
await runtime.shutdown();
|
|
48
|
-
});
|
|
49
|
-
it('returns escaped when no specs produced', async () => {
|
|
50
|
-
getProjectByNameFn.mockReturnValue({ createSpecification: () => null });
|
|
51
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mutineer-worker-'));
|
|
52
|
-
tmpFiles.push(tmp);
|
|
53
|
-
const runtime = createVitestWorkerRuntime({ workerId: 'w2', cwd: tmp });
|
|
54
|
-
await runtime.init();
|
|
55
|
-
const result = await runtime.run({ id: 'mut#2', name: 'm', file: path.join(tmp, 'src.ts'), code: 'export const x=1', line: 1, col: 1 }, [path.join(tmp, 'test.ts')]);
|
|
56
|
-
expect(result.killed).toBe(false);
|
|
57
|
-
await runtime.shutdown();
|
|
58
|
-
});
|
|
59
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vitest Test Runner Adapter
|
|
3
|
-
*
|
|
4
|
-
* Implements the TestRunnerAdapter interface for Vitest.
|
|
5
|
-
* Handles baseline test runs, mutant execution via worker pool,
|
|
6
|
-
* and coverage configuration detection.
|
|
7
|
-
*/
|
|
8
|
-
import type { TestRunnerAdapter, TestRunnerAdapterOptions, MutantPayload, MutantRunResult, BaselineOptions, CoverageConfig } from '../types.js';
|
|
9
|
-
/**
|
|
10
|
-
* Vitest adapter implementation.
|
|
11
|
-
*/
|
|
12
|
-
export declare class VitestAdapter implements TestRunnerAdapter {
|
|
13
|
-
readonly name = "vitest";
|
|
14
|
-
private readonly options;
|
|
15
|
-
private readonly vitestPath;
|
|
16
|
-
private pool;
|
|
17
|
-
private baseArgs;
|
|
18
|
-
constructor(options: TestRunnerAdapterOptions);
|
|
19
|
-
init(concurrencyOverride?: number): Promise<void>;
|
|
20
|
-
runBaseline(tests: readonly string[], options: BaselineOptions): Promise<boolean>;
|
|
21
|
-
runMutant(mutant: MutantPayload, tests: readonly string[]): Promise<MutantRunResult>;
|
|
22
|
-
shutdown(): Promise<void>;
|
|
23
|
-
hasCoverageProvider(): boolean;
|
|
24
|
-
detectCoverageConfig(): Promise<CoverageConfig>;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Check if coverage is requested via CLI args.
|
|
28
|
-
*/
|
|
29
|
-
export declare function isCoverageRequestedInArgs(args: string[]): boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Factory function for creating VitestAdapter instances.
|
|
32
|
-
*/
|
|
33
|
-
export declare function createVitestAdapter(options: TestRunnerAdapterOptions): VitestAdapter;
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vitest Test Runner Adapter
|
|
3
|
-
*
|
|
4
|
-
* Implements the TestRunnerAdapter interface for Vitest.
|
|
5
|
-
* Handles baseline test runs, mutant execution via worker pool,
|
|
6
|
-
* and coverage configuration detection.
|
|
7
|
-
*/
|
|
8
|
-
import fs from 'node:fs/promises';
|
|
9
|
-
import path from 'node:path';
|
|
10
|
-
import { spawn } from 'node:child_process';
|
|
11
|
-
import { createRequire } from 'node:module';
|
|
12
|
-
import { VitestPool } from '../../pool/index.js';
|
|
13
|
-
const require = createRequire(import.meta.url);
|
|
14
|
-
/**
|
|
15
|
-
* Resolve the Vitest CLI entry point.
|
|
16
|
-
*/
|
|
17
|
-
function resolveVitestPath() {
|
|
18
|
-
try {
|
|
19
|
-
return require.resolve('vitest/vitest.mjs');
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
const pkgJson = require.resolve('vitest/package.json');
|
|
23
|
-
return path.join(path.dirname(pkgJson), 'vitest.mjs');
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Strip mutineer-specific CLI args that shouldn't be passed to Vitest.
|
|
28
|
-
*/
|
|
29
|
-
function stripMutineerArgs(args) {
|
|
30
|
-
const out = [];
|
|
31
|
-
const consumeNext = new Set([
|
|
32
|
-
'--concurrency',
|
|
33
|
-
'--progress',
|
|
34
|
-
'--min-kill-percent',
|
|
35
|
-
'--config',
|
|
36
|
-
'-c',
|
|
37
|
-
'--coverage-file',
|
|
38
|
-
]);
|
|
39
|
-
const dropExact = new Set([
|
|
40
|
-
'-m',
|
|
41
|
-
'--mutate',
|
|
42
|
-
'--changed',
|
|
43
|
-
'--changed-with-deps',
|
|
44
|
-
'--only-covered-lines',
|
|
45
|
-
'--per-test-coverage',
|
|
46
|
-
'--perTestCoverage',
|
|
47
|
-
]);
|
|
48
|
-
for (let i = 0; i < args.length; i++) {
|
|
49
|
-
const a = args[i];
|
|
50
|
-
if (dropExact.has(a))
|
|
51
|
-
continue;
|
|
52
|
-
if (consumeNext.has(a)) {
|
|
53
|
-
i++;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (a.startsWith('--min-kill-percent='))
|
|
57
|
-
continue;
|
|
58
|
-
if (a.startsWith('--config=') || a.startsWith('-c='))
|
|
59
|
-
continue;
|
|
60
|
-
out.push(a);
|
|
61
|
-
}
|
|
62
|
-
return out;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Ensure the Vitest config arg is included if specified.
|
|
66
|
-
*/
|
|
67
|
-
function ensureConfigArg(args, vitestConfig, cwd) {
|
|
68
|
-
if (!vitestConfig)
|
|
69
|
-
return args;
|
|
70
|
-
if (args.some((a) => a === '--config' || a === '-c' || a.startsWith('--config=') || a.startsWith('-c='))) {
|
|
71
|
-
return args;
|
|
72
|
-
}
|
|
73
|
-
const resolved = cwd ? path.resolve(cwd, vitestConfig) : vitestConfig;
|
|
74
|
-
return [...args, '--config', resolved];
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Build Vitest CLI arguments for the given mode.
|
|
78
|
-
*/
|
|
79
|
-
function buildVitestArgs(args, mode) {
|
|
80
|
-
const result = [...args];
|
|
81
|
-
if (!result.includes('run') && !result.includes('--run'))
|
|
82
|
-
result.unshift('run');
|
|
83
|
-
if (!result.some((a) => a.startsWith('--watch')))
|
|
84
|
-
result.push('--watch=false');
|
|
85
|
-
if (!result.some((a) => a.startsWith('--passWithNoTests')))
|
|
86
|
-
result.push('--passWithNoTests');
|
|
87
|
-
if (mode === 'baseline-with-coverage') {
|
|
88
|
-
if (!result.some((a) => a.startsWith('--coverage'))) {
|
|
89
|
-
result.push('--coverage.enabled=true', '--coverage.reporter=json');
|
|
90
|
-
}
|
|
91
|
-
if (!result.some((a) => a.startsWith('--coverage.perTest='))) {
|
|
92
|
-
result.push('--coverage.perTest=true');
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Vitest adapter implementation.
|
|
99
|
-
*/
|
|
100
|
-
export class VitestAdapter {
|
|
101
|
-
constructor(options) {
|
|
102
|
-
this.name = 'vitest';
|
|
103
|
-
this.pool = null;
|
|
104
|
-
this.baseArgs = [];
|
|
105
|
-
this.options = options;
|
|
106
|
-
this.vitestPath = resolveVitestPath();
|
|
107
|
-
// Prepare base args by stripping mutineer-specific flags
|
|
108
|
-
const stripped = stripMutineerArgs(options.cliArgs);
|
|
109
|
-
this.baseArgs = ensureConfigArg(stripped, options.config.vitestConfig, options.cwd);
|
|
110
|
-
}
|
|
111
|
-
async init(concurrencyOverride) {
|
|
112
|
-
const workerCount = Math.max(1, concurrencyOverride ?? this.options.concurrency);
|
|
113
|
-
this.pool = new VitestPool({
|
|
114
|
-
cwd: this.options.cwd,
|
|
115
|
-
concurrency: workerCount,
|
|
116
|
-
vitestConfig: this.options.config.vitestConfig,
|
|
117
|
-
timeoutMs: this.options.timeoutMs,
|
|
118
|
-
debug: this.options.debug,
|
|
119
|
-
});
|
|
120
|
-
await this.pool.init();
|
|
121
|
-
}
|
|
122
|
-
async runBaseline(tests, options) {
|
|
123
|
-
const mode = options.collectCoverage ? 'baseline-with-coverage' : 'baseline';
|
|
124
|
-
const args = buildVitestArgs(this.baseArgs, mode);
|
|
125
|
-
return new Promise((resolve) => {
|
|
126
|
-
const env = { ...process.env };
|
|
127
|
-
env.VITEST_WATCH = 'false';
|
|
128
|
-
if (!env.CI)
|
|
129
|
-
env.CI = '1';
|
|
130
|
-
const child = spawn(process.execPath, [this.vitestPath, ...args, ...tests], {
|
|
131
|
-
cwd: this.options.cwd,
|
|
132
|
-
stdio: ['ignore', 'inherit', 'inherit'],
|
|
133
|
-
env,
|
|
134
|
-
});
|
|
135
|
-
child.on('error', (err) => {
|
|
136
|
-
if (this.options.debug)
|
|
137
|
-
console.error('Failed to spawn vitest process', err);
|
|
138
|
-
resolve(false);
|
|
139
|
-
});
|
|
140
|
-
child.on('exit', (code) => {
|
|
141
|
-
resolve(code === 0);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
async runMutant(mutant, tests) {
|
|
146
|
-
if (!this.pool) {
|
|
147
|
-
throw new Error('VitestAdapter not initialized. Call init() first.');
|
|
148
|
-
}
|
|
149
|
-
try {
|
|
150
|
-
const result = await this.pool.run(mutant, [...tests]);
|
|
151
|
-
if (result.error === 'timeout') {
|
|
152
|
-
return {
|
|
153
|
-
status: 'timeout',
|
|
154
|
-
durationMs: result.durationMs,
|
|
155
|
-
error: result.error,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
if (result.error) {
|
|
159
|
-
return {
|
|
160
|
-
status: 'error',
|
|
161
|
-
durationMs: result.durationMs,
|
|
162
|
-
error: result.error,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
return {
|
|
166
|
-
status: result.killed ? 'killed' : 'escaped',
|
|
167
|
-
durationMs: result.durationMs,
|
|
168
|
-
error: result.error,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
return {
|
|
173
|
-
status: 'error',
|
|
174
|
-
durationMs: 0,
|
|
175
|
-
error: err instanceof Error ? err.message : String(err),
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
async shutdown() {
|
|
180
|
-
if (this.pool) {
|
|
181
|
-
await this.pool.shutdown();
|
|
182
|
-
this.pool = null;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
hasCoverageProvider() {
|
|
186
|
-
try {
|
|
187
|
-
require.resolve('@vitest/coverage-v8/package.json', { paths: [this.options.cwd] });
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
async detectCoverageConfig() {
|
|
195
|
-
const configPath = this.options.config.vitestConfig;
|
|
196
|
-
if (!configPath) {
|
|
197
|
-
return { perTestEnabled: false, coverageEnabled: false };
|
|
198
|
-
}
|
|
199
|
-
try {
|
|
200
|
-
const abs = path.isAbsolute(configPath) ? configPath : path.join(this.options.cwd, configPath);
|
|
201
|
-
const content = await fs.readFile(abs, 'utf8');
|
|
202
|
-
const perTestEnabled = /perTest\s*:\s*true/.test(content);
|
|
203
|
-
let coverageEnabled = false;
|
|
204
|
-
if (!/coverage\s*\.\s*enabled\s*:\s*false/.test(content) && !/coverage\s*:\s*false/.test(content)) {
|
|
205
|
-
coverageEnabled = /coverage\s*:/.test(content);
|
|
206
|
-
}
|
|
207
|
-
return { perTestEnabled, coverageEnabled };
|
|
208
|
-
}
|
|
209
|
-
catch {
|
|
210
|
-
return { perTestEnabled: false, coverageEnabled: false };
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Check if coverage is requested via CLI args.
|
|
216
|
-
*/
|
|
217
|
-
export function isCoverageRequestedInArgs(args) {
|
|
218
|
-
let requested = false;
|
|
219
|
-
let disabled = false;
|
|
220
|
-
const isFalsey = (v) => typeof v === 'string' && /^(false|0|off)$/i.test(v);
|
|
221
|
-
for (let i = 0; i < args.length; i++) {
|
|
222
|
-
const arg = args[i];
|
|
223
|
-
if (arg === '--no-coverage') {
|
|
224
|
-
disabled = true;
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
if (arg === '--coverage') {
|
|
228
|
-
requested = true;
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
if (arg === '--coverage.enabled') {
|
|
232
|
-
const next = args[i + 1];
|
|
233
|
-
if (isFalsey(next))
|
|
234
|
-
disabled = true;
|
|
235
|
-
else
|
|
236
|
-
requested = true;
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
if (arg.startsWith('--coverage.enabled=')) {
|
|
240
|
-
const val = arg.slice('--coverage.enabled='.length);
|
|
241
|
-
if (isFalsey(val))
|
|
242
|
-
disabled = true;
|
|
243
|
-
else
|
|
244
|
-
requested = true;
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
if (arg.startsWith('--coverage=')) {
|
|
248
|
-
const val = arg.slice('--coverage='.length);
|
|
249
|
-
if (isFalsey(val))
|
|
250
|
-
disabled = true;
|
|
251
|
-
else
|
|
252
|
-
requested = true;
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
if (arg.startsWith('--coverage.')) {
|
|
256
|
-
requested = true;
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return requested && !disabled;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Factory function for creating VitestAdapter instances.
|
|
264
|
-
*/
|
|
265
|
-
export function createVitestAdapter(options) {
|
|
266
|
-
return new VitestAdapter(options);
|
|
267
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { MutantPayload } from '../types.js';
|
|
2
|
-
import type { MutantRunSummary } from '../../../types/mutant.js';
|
|
3
|
-
declare global {
|
|
4
|
-
var __mutineer_redirect__: {
|
|
5
|
-
from: string | null;
|
|
6
|
-
to: string | null;
|
|
7
|
-
} | undefined;
|
|
8
|
-
}
|
|
9
|
-
export interface VitestWorkerRuntimeOptions {
|
|
10
|
-
workerId: string;
|
|
11
|
-
cwd: string;
|
|
12
|
-
vitestConfigPath?: string;
|
|
13
|
-
debug?: boolean;
|
|
14
|
-
}
|
|
15
|
-
export declare class VitestWorkerRuntime {
|
|
16
|
-
private readonly options;
|
|
17
|
-
private vitest;
|
|
18
|
-
private readonly debugEnabled;
|
|
19
|
-
constructor(options: VitestWorkerRuntimeOptions);
|
|
20
|
-
private log;
|
|
21
|
-
init(): Promise<void>;
|
|
22
|
-
shutdown(): Promise<void>;
|
|
23
|
-
run(mutant: MutantPayload, tests: string[]): Promise<MutantRunSummary>;
|
|
24
|
-
}
|
|
25
|
-
export declare function createVitestWorkerRuntime(options: VitestWorkerRuntimeOptions): VitestWorkerRuntime;
|