mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo.git
synced 2026-09-21 16:52:01 -07:00
Narrow gherkin mutation runner-worker to the changed scenario
The DB soft Gherkin mutation re-ran the whole feature for every mutant; with a fresh LevelDB world per example, a 48-mutation feature exceeded 900s. The runner-worker now diffs the mutated IR against the base feature at <work>/base/feature.json (derived from the mutation path, since job.work_dir may be mutation-specific) and runs only the changed scenario/example. Safe because each scenario uses an isolated world. The same feature now completes in ~74s. By architect.
This commit is contained in:
@@ -22,11 +22,43 @@
|
||||
|
||||
import readline from 'node:readline'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
console.log = (...args) => console.error('[worker]', ...args)
|
||||
|
||||
const { runFeature } = await import('./runtime.js')
|
||||
|
||||
// A gherkin mutation changes one example value in one scenario. Every scenario
|
||||
// runs in its own isolated LevelDB world, so the other scenarios cannot be
|
||||
// affected by the mutation. Narrow the IR to the changed scenario/example so a
|
||||
// mutant does not re-run the whole feature. Falls back to the full IR when the
|
||||
// base feature is missing or its shape differs.
|
||||
function narrowToChanged (ir, basePath) {
|
||||
try {
|
||||
const base = JSON.parse(fs.readFileSync(basePath, 'utf8'))
|
||||
if (!base || !Array.isArray(base.scenarios) || base.scenarios.length !== ir.scenarios.length) return ir
|
||||
|
||||
for (let i = 0; i < ir.scenarios.length; i++) {
|
||||
const scenario = ir.scenarios[i]
|
||||
const original = base.scenarios[i]
|
||||
if (JSON.stringify(scenario) === JSON.stringify(original)) continue
|
||||
|
||||
const examples = Array.isArray(scenario.examples) ? scenario.examples : []
|
||||
const originalExamples = Array.isArray(original.examples) ? original.examples : []
|
||||
if (examples.length === originalExamples.length) {
|
||||
const changed = examples.filter((ex, j) => JSON.stringify(ex) !== JSON.stringify(originalExamples[j]))
|
||||
if (changed.length > 0) {
|
||||
return { ...ir, scenarios: [{ ...scenario, examples: changed }] }
|
||||
}
|
||||
}
|
||||
return { ...ir, scenarios: [scenario] }
|
||||
}
|
||||
return ir
|
||||
} catch (err) {
|
||||
return ir
|
||||
}
|
||||
}
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
@@ -49,7 +81,12 @@ rl.on('line', async (line) => {
|
||||
|
||||
try {
|
||||
const ir = JSON.parse(fs.readFileSync(job.feature_json, 'utf8'))
|
||||
const report = await runFeature(ir)
|
||||
// The mutated feature lives at <work>/mutations/<id>/feature.json, so the
|
||||
// base feature is two levels up. Do not trust job.work_dir (the mutator may
|
||||
// pass the mutation-specific directory).
|
||||
const basePath = path.resolve(path.dirname(job.feature_json), '..', '..', 'base', 'feature.json')
|
||||
const narrowed = narrowToChanged(ir, basePath)
|
||||
const report = await runFeature(narrowed)
|
||||
respond({
|
||||
id: job.id,
|
||||
outcome: report.failures === 0 ? 'test_success' : 'test_failure',
|
||||
|
||||
Reference in New Issue
Block a user