@mutineerjs/mutineer 0.2.4 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +48 -42
  2. package/dist/bin/mutineer.js +0 -0
  3. package/dist/mutators/__tests__/operator.spec.js +169 -0
  4. package/dist/mutators/__tests__/registry.spec.js +6 -0
  5. package/dist/mutators/__tests__/return-value.spec.js +239 -0
  6. package/dist/mutators/__tests__/utils.spec.js +68 -1
  7. package/dist/mutators/operator.d.ts +25 -0
  8. package/dist/mutators/operator.js +50 -0
  9. package/dist/mutators/registry.d.ts +6 -28
  10. package/dist/mutators/registry.js +14 -66
  11. package/dist/mutators/return-value.d.ts +39 -0
  12. package/dist/mutators/return-value.js +104 -0
  13. package/dist/mutators/utils.d.ts +21 -0
  14. package/dist/mutators/utils.js +44 -27
  15. package/dist/runner/__tests__/pool-executor.spec.js +2 -4
  16. package/dist/runner/__tests__/variants.spec.js +3 -1
  17. package/dist/runner/discover.js +2 -1
  18. package/dist/runner/jest/__tests__/adapter.spec.js +1 -1
  19. package/dist/runner/jest/__tests__/pool.spec.js +5 -6
  20. package/dist/runner/jest/__tests__/worker-runtime.spec.js +2 -2
  21. package/dist/runner/jest/adapter.js +1 -1
  22. package/dist/runner/jest/pool.d.ts +1 -1
  23. package/dist/runner/jest/pool.js +6 -6
  24. package/dist/runner/jest/worker.mjs +1 -1
  25. package/dist/runner/pool-executor.js +1 -1
  26. package/dist/runner/shared/__tests__/redirect-state.spec.js +3 -3
  27. package/dist/runner/shared/index.d.ts +1 -1
  28. package/dist/runner/shared/index.js +1 -1
  29. package/dist/runner/shared/redirect-state.d.ts +2 -2
  30. package/dist/runner/shared/redirect-state.js +4 -4
  31. package/dist/runner/types.d.ts +1 -1
  32. package/dist/runner/vitest/__tests__/adapter.spec.js +1 -1
  33. package/dist/runner/vitest/__tests__/pool.spec.js +1 -1
  34. package/dist/runner/vitest/__tests__/redirect-loader.spec.js +83 -1
  35. package/dist/runner/vitest/__tests__/worker-runtime.spec.js +84 -0
  36. package/dist/runner/vitest/adapter.js +1 -1
  37. package/dist/runner/vitest/index.d.ts +0 -1
  38. package/dist/runner/vitest/index.js +0 -1
  39. package/dist/runner/vitest/pool.d.ts +1 -1
  40. package/dist/runner/vitest/pool.js +7 -7
  41. package/dist/runner/vitest/redirect-loader.d.ts +1 -1
  42. package/dist/runner/vitest/redirect-loader.js +1 -1
  43. package/dist/runner/vitest/worker-runtime.js +3 -3
  44. package/dist/runner/vitest/worker.mjs +1 -1
  45. package/dist/utils/__tests__/coverage.spec.js +167 -0
  46. package/dist/utils/__tests__/progress.spec.js +96 -0
  47. package/package.json +71 -22
  48. package/dist/admin/assets/index-B7nXq-e7.js +0 -32
  49. package/dist/admin/assets/index-B7nXq-e7.js.map +0 -1
  50. package/dist/admin/assets/index-BDQLkBUE.js +0 -32
  51. package/dist/admin/assets/index-BDQLkBUE.js.map +0 -1
  52. package/dist/admin/assets/index-DVkP-Tc7.css +0 -1
  53. package/dist/admin/index.html +0 -13
  54. package/dist/admin/server/admin.d.ts +0 -6
  55. package/dist/admin/server/admin.js +0 -234
  56. package/dist/bin/mutate-vitest.d.ts +0 -2
  57. package/dist/bin/mutate-vitest.js +0 -90
  58. package/dist/plugin/viteMutate.d.ts +0 -15
  59. package/dist/plugin/viteMutate.js +0 -52
  60. package/dist/plugin/vitest.setup.d.ts +0 -47
  61. package/dist/plugin/vitest.setup.js +0 -118
  62. package/dist/plugin/withVitest.d.ts +0 -13
  63. package/dist/plugin/withVitest.js +0 -30
  64. package/dist/runner/__tests__/orchestrator.spec.js +0 -55
  65. package/dist/runner/adapters/__tests__/jest.spec.js +0 -88
  66. package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.d.ts +0 -1
  67. package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.js +0 -59
  68. package/dist/runner/adapters/__tests__/vitest.spec.d.ts +0 -1
  69. package/dist/runner/adapters/__tests__/vitest.spec.js +0 -118
  70. package/dist/runner/adapters/index.d.ts +0 -10
  71. package/dist/runner/adapters/index.js +0 -9
  72. package/dist/runner/adapters/jest/__tests__/index.spec.d.ts +0 -1
  73. package/dist/runner/adapters/jest/__tests__/index.spec.js +0 -88
  74. package/dist/runner/adapters/jest/index.d.ts +0 -24
  75. package/dist/runner/adapters/jest/index.js +0 -216
  76. package/dist/runner/adapters/jest/worker-runtime.d.ts +0 -37
  77. package/dist/runner/adapters/jest/worker-runtime.js +0 -171
  78. package/dist/runner/adapters/jest-worker-runtime.d.ts +0 -37
  79. package/dist/runner/adapters/jest-worker-runtime.js +0 -171
  80. package/dist/runner/adapters/jest.d.ts +0 -24
  81. package/dist/runner/adapters/jest.js +0 -216
  82. package/dist/runner/adapters/types.d.ts +0 -89
  83. package/dist/runner/adapters/types.js +0 -8
  84. package/dist/runner/adapters/vitest/__tests__/index.spec.d.ts +0 -1
  85. package/dist/runner/adapters/vitest/__tests__/index.spec.js +0 -118
  86. package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.d.ts +0 -1
  87. package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.js +0 -59
  88. package/dist/runner/adapters/vitest/index.d.ts +0 -33
  89. package/dist/runner/adapters/vitest/index.js +0 -267
  90. package/dist/runner/adapters/vitest/worker-runtime.d.ts +0 -25
  91. package/dist/runner/adapters/vitest/worker-runtime.js +0 -118
  92. package/dist/runner/adapters/vitest-worker-runtime.d.ts +0 -25
  93. package/dist/runner/adapters/vitest-worker-runtime.js +0 -118
  94. package/dist/runner/adapters/vitest.d.ts +0 -33
  95. package/dist/runner/adapters/vitest.js +0 -267
  96. package/dist/runner/pool/__tests__/index.spec.d.ts +0 -1
  97. package/dist/runner/pool/__tests__/index.spec.js +0 -83
  98. package/dist/runner/pool/__tests__/pool-plugin.spec.d.ts +0 -1
  99. package/dist/runner/pool/__tests__/pool-plugin.spec.js +0 -59
  100. package/dist/runner/pool/__tests__/pool-redirect-loader.spec.d.ts +0 -1
  101. package/dist/runner/pool/__tests__/pool-redirect-loader.spec.js +0 -78
  102. package/dist/runner/pool/index.d.ts +0 -8
  103. package/dist/runner/pool/index.js +0 -9
  104. package/dist/runner/pool/jest/pool.d.ts +0 -52
  105. package/dist/runner/pool/jest/pool.js +0 -309
  106. package/dist/runner/pool/jest/worker.d.mts +0 -1
  107. package/dist/runner/pool/jest/worker.mjs +0 -60
  108. package/dist/runner/pool/jest-pool.d.ts +0 -52
  109. package/dist/runner/pool/jest-pool.js +0 -309
  110. package/dist/runner/pool/jest-worker.d.mts +0 -1
  111. package/dist/runner/pool/jest-worker.mjs +0 -60
  112. package/dist/runner/pool/plugin.d.ts +0 -18
  113. package/dist/runner/pool/plugin.js +0 -60
  114. package/dist/runner/pool/pool-plugin.d.ts +0 -18
  115. package/dist/runner/pool/pool-plugin.js +0 -60
  116. package/dist/runner/pool/pool-redirect-loader.d.ts +0 -19
  117. package/dist/runner/pool/pool-redirect-loader.js +0 -116
  118. package/dist/runner/pool/pool-redirect-loader.mjs +0 -146
  119. package/dist/runner/pool/redirect-loader.d.ts +0 -19
  120. package/dist/runner/pool/redirect-loader.js +0 -116
  121. package/dist/runner/pool/vitest/pool.d.ts +0 -70
  122. package/dist/runner/pool/vitest/pool.js +0 -376
  123. package/dist/runner/pool/vitest/worker.d.mts +0 -15
  124. package/dist/runner/pool/vitest/worker.mjs +0 -96
  125. package/dist/runner/pool/vitest-worker.d.mts +0 -15
  126. package/dist/runner/pool/vitest-worker.mjs +0 -96
  127. package/dist/runner/shared-module-redirect.d.ts +0 -56
  128. package/dist/runner/shared-module-redirect.js +0 -84
  129. package/dist/types/api.d.ts +0 -20
  130. package/dist/types/api.js +0 -1
  131. /package/dist/{runner/__tests__/orchestrator.spec.d.ts → mutators/__tests__/operator.spec.d.ts} +0 -0
  132. /package/dist/{runner/adapters/__tests__/jest.spec.d.ts → mutators/__tests__/return-value.spec.d.ts} +0 -0
@@ -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;
@@ -1,118 +0,0 @@
1
- import { createVitest } from 'vitest/node';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { poolMutineerPlugin } from '../../pool/plugin.js';
5
- // Shared state for the loader/plugin redirect
6
- globalThis.__mutineer_redirect__ = { from: null, to: null };
7
- function getMutantFilePath(originalFile, mutantId) {
8
- const dir = path.dirname(originalFile);
9
- const ext = path.extname(originalFile);
10
- const basename = path.basename(originalFile, ext);
11
- const mutineerDir = path.join(dir, '__mutineer__');
12
- if (!fs.existsSync(mutineerDir)) {
13
- fs.mkdirSync(mutineerDir, { recursive: true });
14
- }
15
- const idMatch = mutantId.match(/#(\d+)$/);
16
- const suffix = idMatch ? idMatch[1] : mutantId.replace(/[^a-zA-Z0-9]/g, '_').slice(0, 20);
17
- return path.join(mutineerDir, `${basename}_${suffix}${ext}`);
18
- }
19
- export class VitestWorkerRuntime {
20
- constructor(options) {
21
- this.options = options;
22
- this.vitest = null;
23
- this.debugEnabled = options.debug ?? false;
24
- }
25
- log(msg) {
26
- if (this.debugEnabled) {
27
- console.error(`[vitest-worker] ${msg}`);
28
- }
29
- }
30
- async init() {
31
- try {
32
- this.vitest = await createVitest('test', {
33
- watch: true,
34
- reporters: ['dot'],
35
- silent: true,
36
- pool: 'forks',
37
- bail: 1,
38
- ...(this.options.vitestConfigPath ? { config: this.options.vitestConfigPath } : {}),
39
- }, {
40
- plugins: [poolMutineerPlugin()],
41
- });
42
- await this.vitest.init();
43
- this.log(`Vitest initialized for worker ${this.options.workerId}`);
44
- }
45
- catch (err) {
46
- console.error('[vitest-worker] Failed to initialize Vitest:', err);
47
- throw err;
48
- }
49
- }
50
- async shutdown() {
51
- if (!this.vitest)
52
- return;
53
- await this.vitest.close();
54
- this.vitest = null;
55
- }
56
- async run(mutant, tests) {
57
- if (!this.vitest) {
58
- throw new Error('Vitest runtime not initialized');
59
- }
60
- const start = Date.now();
61
- try {
62
- const mutantPath = getMutantFilePath(mutant.file, mutant.id);
63
- fs.writeFileSync(mutantPath, mutant.code, 'utf8');
64
- this.log(`Wrote mutant to ${mutantPath}`);
65
- globalThis.__mutineer_redirect__ = {
66
- from: path.resolve(mutant.file),
67
- to: mutantPath,
68
- };
69
- this.vitest.invalidateFile(mutant.file);
70
- this.log(`Invalidated ${mutant.file}`);
71
- const specs = [];
72
- for (const testFile of tests) {
73
- const spec = this.vitest.getProjectByName('')?.createSpecification(testFile);
74
- if (spec)
75
- specs.push(spec);
76
- }
77
- if (specs.length === 0) {
78
- return {
79
- killed: false,
80
- durationMs: Date.now() - start,
81
- };
82
- }
83
- this.log(`Running ${specs.length} test specs`);
84
- const results = await this.vitest.runTestSpecifications(specs);
85
- const requestedModules = new Set(specs.map((s) => s.moduleId));
86
- const relevantModules = results.testModules.filter((mod) => requestedModules.has(mod.moduleId));
87
- const modulesForDecision = relevantModules.length ? relevantModules : results.testModules;
88
- const killed = modulesForDecision.some((mod) => !mod.ok());
89
- return {
90
- killed,
91
- durationMs: Date.now() - start,
92
- };
93
- }
94
- catch (err) {
95
- return {
96
- killed: true,
97
- durationMs: Date.now() - start,
98
- error: err instanceof Error ? err.message : String(err),
99
- };
100
- }
101
- finally {
102
- // Clear redirect and clean up temp file
103
- const redirect = globalThis.__mutineer_redirect__;
104
- globalThis.__mutineer_redirect__ = { from: null, to: null };
105
- try {
106
- if (redirect?.to) {
107
- fs.rmSync(redirect.to, { force: true });
108
- }
109
- }
110
- catch {
111
- // ignore
112
- }
113
- }
114
- }
115
- }
116
- export function createVitestWorkerRuntime(options) {
117
- return new VitestWorkerRuntime(options);
118
- }
@@ -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;
@@ -1,118 +0,0 @@
1
- import { createVitest } from 'vitest/node';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { poolMutineerPlugin } from '../pool/pool-plugin.js';
5
- // Shared state for the loader/plugin redirect
6
- globalThis.__mutineer_redirect__ = { from: null, to: null };
7
- function getMutantFilePath(originalFile, mutantId) {
8
- const dir = path.dirname(originalFile);
9
- const ext = path.extname(originalFile);
10
- const basename = path.basename(originalFile, ext);
11
- const mutineerDir = path.join(dir, '__mutineer__');
12
- if (!fs.existsSync(mutineerDir)) {
13
- fs.mkdirSync(mutineerDir, { recursive: true });
14
- }
15
- const idMatch = mutantId.match(/#(\d+)$/);
16
- const suffix = idMatch ? idMatch[1] : mutantId.replace(/[^a-zA-Z0-9]/g, '_').slice(0, 20);
17
- return path.join(mutineerDir, `${basename}_${suffix}${ext}`);
18
- }
19
- export class VitestWorkerRuntime {
20
- constructor(options) {
21
- this.options = options;
22
- this.vitest = null;
23
- this.debugEnabled = options.debug ?? false;
24
- }
25
- log(msg) {
26
- if (this.debugEnabled) {
27
- console.error(`[vitest-worker] ${msg}`);
28
- }
29
- }
30
- async init() {
31
- try {
32
- this.vitest = await createVitest('test', {
33
- watch: true,
34
- reporters: ['dot'],
35
- silent: true,
36
- pool: 'forks',
37
- bail: 1,
38
- ...(this.options.vitestConfigPath ? { config: this.options.vitestConfigPath } : {}),
39
- }, {
40
- plugins: [poolMutineerPlugin()],
41
- });
42
- await this.vitest.init();
43
- this.log(`Vitest initialized for worker ${this.options.workerId}`);
44
- }
45
- catch (err) {
46
- console.error('[vitest-worker] Failed to initialize Vitest:', err);
47
- throw err;
48
- }
49
- }
50
- async shutdown() {
51
- if (!this.vitest)
52
- return;
53
- await this.vitest.close();
54
- this.vitest = null;
55
- }
56
- async run(mutant, tests) {
57
- if (!this.vitest) {
58
- throw new Error('Vitest runtime not initialized');
59
- }
60
- const start = Date.now();
61
- try {
62
- const mutantPath = getMutantFilePath(mutant.file, mutant.id);
63
- fs.writeFileSync(mutantPath, mutant.code, 'utf8');
64
- this.log(`Wrote mutant to ${mutantPath}`);
65
- globalThis.__mutineer_redirect__ = {
66
- from: path.resolve(mutant.file),
67
- to: mutantPath,
68
- };
69
- this.vitest.invalidateFile(mutant.file);
70
- this.log(`Invalidated ${mutant.file}`);
71
- const specs = [];
72
- for (const testFile of tests) {
73
- const spec = this.vitest.getProjectByName('')?.createSpecification(testFile);
74
- if (spec)
75
- specs.push(spec);
76
- }
77
- if (specs.length === 0) {
78
- return {
79
- killed: false,
80
- durationMs: Date.now() - start,
81
- };
82
- }
83
- this.log(`Running ${specs.length} test specs`);
84
- const results = await this.vitest.runTestSpecifications(specs);
85
- const requestedModules = new Set(specs.map((s) => s.moduleId));
86
- const relevantModules = results.testModules.filter((mod) => requestedModules.has(mod.moduleId));
87
- const modulesForDecision = relevantModules.length ? relevantModules : results.testModules;
88
- const killed = modulesForDecision.some((mod) => !mod.ok());
89
- return {
90
- killed,
91
- durationMs: Date.now() - start,
92
- };
93
- }
94
- catch (err) {
95
- return {
96
- killed: true,
97
- durationMs: Date.now() - start,
98
- error: err instanceof Error ? err.message : String(err),
99
- };
100
- }
101
- finally {
102
- // Clear redirect and clean up temp file
103
- const redirect = globalThis.__mutineer_redirect__;
104
- globalThis.__mutineer_redirect__ = { from: null, to: null };
105
- try {
106
- if (redirect?.to) {
107
- fs.rmSync(redirect.to, { force: true });
108
- }
109
- }
110
- catch {
111
- // ignore
112
- }
113
- }
114
- }
115
- }
116
- export function createVitestWorkerRuntime(options) {
117
- return new VitestWorkerRuntime(options);
118
- }
@@ -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;