#!/usr/bin/env node /** * mutate-runner (Level B, P18) — живая мутация одного файла под один тест. * baseline-GREEN до мутации (SE-LB-2: краснота атрибутируема мутации, не пред-существующему провалу); * мутация IN-PLACE с железобетонным восстановлением (SE-LB-3): тест импортирует РЕАЛЬНЫЙ путь, поэтому * мутанта надо положить туда же; оригинал держим в памяти и пишем обратно в finally + сверяем восстановление. * Утечка мутанта при жёстком kill ловится потребителем по несовпадению отпечатка (defense-in-depth). * classifyMutationResult — чистая: убит ⇔ ≥1 валидный (не loadError) мутант покраснел. */ import { readFileSync, writeFileSync } from 'node:fs'; export function classifyMutationResult({ baselineGreen, mutantOutcomes = [] }) { if (baselineGreen !== true) return { mutationKilled: false, reason: 'baseline-not-green' }; const valid = (mutantOutcomes || []).filter((o) => o && o.loadError !== true); if (valid.length === 0) return { mutationKilled: false, reason: 'no-valid-mutants' }; const killed = valid.some((o) => o.allGreen === false); return killed ? { mutationKilled: true, reason: 'ok' } : { mutationKilled: false, reason: 'mutation-survived' }; } /** * Прогнать мутацию для filePath под testFile. generate/runTest инъектируются (юнит-тесты — фейки; * live main подаёт generateMutants и (tf)=>runVitestJson(tf, gitCwd)). testFile передаётся в runTest. */ export function runMutationForFile({ filePath, testFile, generate, runTest }) { const original = readFileSync(filePath, 'utf-8'); const baseline = runTest(testFile); if (!(baseline.allGreen === true && baseline.numPassed > 0)) { return { baselineGreen: false, testCount: baseline.numPassed || 0, mutantOutcomes: [] }; } const mutants = generate(original) || []; const mutantOutcomes = []; try { for (const m of mutants) { writeFileSync(filePath, m.mutated); const r = runTest(testFile); mutantOutcomes.push({ label: m.label, allGreen: r.allGreen, loadError: r.loadError }); } } finally { writeFileSync(filePath, original); // восстановление гарантировано } if (readFileSync(filePath, 'utf-8') !== original) { throw new Error('mutate-runner: восстановление файла не удалось (fail-CLOSE)'); } return { baselineGreen: true, testCount: baseline.numPassed, mutantOutcomes }; }