65 lines
3.9 KiB
JavaScript
65 lines
3.9 KiB
JavaScript
// Filbert's row 57 mutants: each edits one file in the mutant worktree, runs
|
||
// the bus and webui suites, and restores the file. Usage: node run.mjs <worktree>
|
||
import { readFileSync, writeFileSync, appendFileSync } from 'node:fs';
|
||
import { spawnSync } from 'node:child_process';
|
||
import { join } from 'node:path';
|
||
|
||
const W = process.argv[2];
|
||
const OUT = new URL('.', import.meta.url).pathname;
|
||
const B = 'packages/bus/src/broker.mjs';
|
||
const S = 'packages/webui/src/serve.mjs';
|
||
const V = 'packages/bus/src/views.mjs';
|
||
const mutants = [
|
||
['role-allowed', B, "if (!s.human && !s.reader) fail('human-required');", ''],
|
||
['roles-with-human', B, "AND route_to<>'human' AND", 'AND'],
|
||
['roles-with-closed', B, ` AND NOT EXISTS(SELECT 1 FROM decision_events x WHERE x.decision=d.id AND x.op IN \${CLOSED}) ORDER BY seq`, ' ORDER BY seq'],
|
||
['roles-other-business', B, "WHERE business=? AND route_to<>'human'", "WHERE ? IS NOT NULL AND route_to<>'human'"],
|
||
['recent-other-business', B, 'WHERE d.business=? ORDER BY x.seq DESC', 'WHERE ? IS NOT NULL ORDER BY x.seq DESC'],
|
||
['recent-oldest-first', B, 'ORDER BY x.seq DESC LIMIT', 'ORDER BY x.seq ASC LIMIT'],
|
||
['recent-by-raise', B, 'ORDER BY x.seq DESC LIMIT', 'ORDER BY d.seq DESC LIMIT'],
|
||
['recent-no-limit', B, ' LIMIT ${RECENT}`', '`'],
|
||
['recent-limit-plus-one', B, ' LIMIT ${RECENT}`', ' LIMIT ${RECENT + 1}`'],
|
||
['recent-resolved-only', B, `JOIN decision_events x ON x.decision=d.id AND x.op IN \${CLOSED}`, `JOIN decision_events x ON x.decision=d.id AND x.op IN ('resolved')`],
|
||
['recent-no-closed', B, '({ ...this.#decisionView(d), closed: this.#closed(d.id) })', '({ ...this.#decisionView(d) })'],
|
||
['loose-keys', B, "keys(a, ['list'], ['list']);\n if (a.list === 'roles')", "keys(a, ['list', 'limit'], ['list']);\n if (a.list === 'roles')"],
|
||
['unknown-list-empty', B, " .map((d) => ({ ...this.#decisionView(d), closed: this.#closed(d.id) }));\n fail('invalid-request');", " .map((d) => ({ ...this.#decisionView(d), closed: this.#closed(d.id) }));\n return [];"],
|
||
['reader-refused', B, "'trail', 'decisions'].includes(request.verb))", "'trail'].includes(request.verb))"],
|
||
['web-no-list-check', S, "if (!DECISION_LISTS.includes(arg)) return", 'if (false) return'],
|
||
['web-no-verb', S, "const BUS_VERBS = ['inbox', 'tasks', 'agents', 'trail', 'decisions'];", "const BUS_VERBS = ['inbox', 'tasks', 'agents', 'trail'];"],
|
||
['views-fixed-list', V, "decisions: (list) => client.call('decisions', { list }),", "decisions: () => client.call('decisions', { list: 'roles' }),"],
|
||
];
|
||
|
||
const suites = [
|
||
['bus', ['--test', 'packages/bus/tests/*.test.mjs'], W],
|
||
['webui', ['--test', 'tests/'], join(W, 'packages/webui')],
|
||
];
|
||
const env = { ...process.env, TMPDIR: process.env.HOME + '/filbert-scratch/tmp', DOCKER_HOST: 'unix:///nonexistent.sock' };
|
||
const log = join(OUT, process.argv[4] || 'result.txt');
|
||
writeFileSync(log, '');
|
||
const only = process.argv[3] ? process.argv[3].split(',') : null;
|
||
for (const [name, file, from, to] of mutants) {
|
||
if (only && !only.includes(name)) continue;
|
||
const path = join(W, file);
|
||
const orig = readFileSync(path, 'utf8');
|
||
if (orig.split(from).length !== 2) {
|
||
appendFileSync(log, `${name}: PATTERN NOT UNIQUE\n`);
|
||
continue;
|
||
}
|
||
writeFileSync(path, orig.replace(from, to));
|
||
const parts = [];
|
||
try {
|
||
for (const [label, args, cwd] of suites) {
|
||
const r = spawnSync('node', args, { cwd, env, encoding: 'utf8', timeout: 600000 });
|
||
const out = r.stdout + r.stderr;
|
||
writeFileSync(join(OUT, `${name}.${label}.log`), out);
|
||
const fail = (out.match(/^ℹ fail (\d+)/m) || [])[1];
|
||
const fails = [...out.matchAll(/^\s*✖ (.+?) \(\d/gm)].map((m) => m[1]).filter((t) => !/^tests?\//.test(t));
|
||
parts.push(`${label} rc=${r.status} fail=${fail} ${[...new Set(fails)].slice(0, 4).join(' | ')}`);
|
||
}
|
||
} finally {
|
||
writeFileSync(path, orig);
|
||
}
|
||
appendFileSync(log, `${name}: ${parts.join(' ; ')}\n`);
|
||
}
|
||
appendFileSync(log, 'done\n');
|