Ledger skips T3 reasoning summaries and reads issue pages with a
10-page ceiling. Candidate manifest, patch against cc48b124f, mutants
(both killed) and a passing full gate.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
54 lines
2.6 KiB
Python
Executable File
54 lines
2.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
# Row 113 (#1607): the brief's two mutants, one per fix.
|
||
# m1 drops the id prefix check in t3.mjs, so every role "reasoning" row is
|
||
# skipped, including one whose id doesn't start "reasoning:".
|
||
# m2 stops the issue read after page 1, as if it were still one call.
|
||
# Each runs the FULL ledger suite (node --test tests/*.test.mjs in
|
||
# packages/ledger) and restores the source from the .orig copy whatever happens.
|
||
# Usage: mutants.py <worktree> <log dir>
|
||
import os, shutil, subprocess, sys, signal
|
||
|
||
WT, LOGS = sys.argv[1], sys.argv[2]
|
||
MUTANTS = {
|
||
'm1': ('packages/ledger/src/t3.mjs',
|
||
"if (m.role === 'reasoning' && typeof m.message_id === 'string' && m.message_id.startsWith('reasoning:')) {",
|
||
"if (m.role === 'reasoning') {"),
|
||
'm2': ('packages/ledger/src/ledger.mjs',
|
||
" if (rows.length < PAGE_SIZE) return { issues: issues.filter(i => !i.pull_request), pages: page };\n",
|
||
" return { issues: issues.filter(i => !i.pull_request), pages: page };\n"),
|
||
}
|
||
os.makedirs(LOGS, exist_ok=True)
|
||
env = dict(os.environ, TMPDIR=os.path.expanduser('~/darkwing-scratch/r113/tmp'))
|
||
for name, (rel, cur, new) in MUTANTS.items():
|
||
src = os.path.join(WT, rel)
|
||
text = open(src).read()
|
||
if text.count(cur) != 1:
|
||
sys.exit(f'STOP {name}: the target text in {rel} is not unique or has moved')
|
||
if os.path.exists(src + '.orig'):
|
||
sys.exit(f'STOP {rel}.orig already exists')
|
||
log_path = os.path.join(LOGS, f'mutant-{name}.log')
|
||
shutil.copy2(src, src + '.orig')
|
||
try:
|
||
open(src, 'w').write(text.replace(cur, new))
|
||
with open(log_path, 'w') as out:
|
||
p = subprocess.Popen('node --test tests/*.test.mjs', shell=True, cwd=os.path.join(WT, 'packages/ledger'),
|
||
stdout=out, stderr=subprocess.STDOUT, env=env, start_new_session=True)
|
||
try:
|
||
rc = p.wait(timeout=600)
|
||
except subprocess.TimeoutExpired:
|
||
os.killpg(p.pid, signal.SIGKILL)
|
||
p.wait()
|
||
rc = 'timeout'
|
||
finally:
|
||
shutil.move(src + '.orig', src)
|
||
restored = open(src).read() == text
|
||
log = open(log_path).read()
|
||
counts = [l for l in log.splitlines() if l.startswith(('ℹ pass', 'ℹ fail', 'ℹ skipped'))]
|
||
failed = [l.strip() for l in log.splitlines() if l.lstrip().startswith('✖') and 'failing tests' not in l]
|
||
print(f'mutant {name} rc={rc} {" ".join(counts)} restored={restored}')
|
||
for f in sorted(set(failed)):
|
||
print(' ' + f)
|
||
print('KILLED' if rc != 0 else 'SURVIVED')
|
||
if not restored:
|
||
sys.exit(f'STOP {rel} was not restored')
|