Candidate manifest, patch against 559ef2287, build notes, five killed
mutants and the targeted gate (seat, cli, harness, two scripts).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
68 lines
3.3 KiB
Python
Executable File
68 lines
3.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
# Row 117 (#1611): the brief's five mutants of the fetched outcome, one per check.
|
||
# m1 fetches for a third reason: a failed status becomes a fetched reason.
|
||
# m2 merges after the fetch in a fetched clone.
|
||
# m3 drops the fetch for a fetched clone.
|
||
# m4 reports the clone's HEAD as the origin commit.
|
||
# m5 fetches before the gitlink check (and so before the root checks too).
|
||
# Each runs the FULL seat suite (node --test tests/*.test.mjs in packages/seat)
|
||
# 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]
|
||
SRC = 'packages/seat/src/checkout.mjs'
|
||
FETCH = '[...inClone(dest), "fetch", "-q", "--no-tags", "--no-recurse-submodules", "origin"]'
|
||
MERGE = '[...inClone(dest), "merge", "-q", "--ff-only", "--no-edit", `refs/remotes/origin/${at.branch}`]'
|
||
MUTANTS = {
|
||
'm1': (SRC,
|
||
' if (!status.ok) return { outcome: "kept", reason: "status-failed" };\n',
|
||
' if (!status.ok) reason = "status-failed";\n'),
|
||
'm2': (SRC,
|
||
' if (reason) {\n',
|
||
f' if (reason) {{\n await g({MERGE});\n'),
|
||
'm3': (SRC,
|
||
f' if (!(await g({FETCH})).ok)',
|
||
f' if (!reason && !(await g({FETCH})).ok)'),
|
||
'm4': (SRC,
|
||
'origin: { branch: at.branch, commit: origin.ok ? origin.out.trim() : null } };',
|
||
'origin: { branch: at.branch, commit: before } };'),
|
||
'm5': (SRC,
|
||
' // ls-files reads only the index: no filter, hook or submodule runs.\n',
|
||
f' await g({FETCH});\n // ls-files reads only the index: no filter, hook or submodule runs.\n'),
|
||
}
|
||
os.makedirs(LOGS, exist_ok=True)
|
||
env = dict(os.environ, TMPDIR=os.path.expanduser('~/darkwing-scratch/r117/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/seat'),
|
||
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')
|