Files
brain/tools/mutate-runner.mjs
T

46 lines
2.7 KiB
JavaScript

#!/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 };
}