#!/usr/bin/env python3 """Row 130 mutant: drop the extra stderr line from run() in cli.mjs, run the cli suite, and restore the file from .orig with a sha256 check.""" import hashlib, os, re, shutil, signal, subprocess WT = os.path.expanduser('~/darkwing-scratch/r130/wt') PKT = os.path.expanduser('~/darkwing-scratch/r130/pkt') TMP = os.path.expanduser('~/darkwing-scratch/r130/tmp') F = 'packages/cli/src/cli.mjs' LINE = ' if (OTHER_TREE_CODES.has(error.code)) io.stderr.write(`mosaic: ${OTHER_TREE}\\n`);\n' M = [('m1-drop-other-tree-line', LINE)] def sha(p): return hashlib.sha256(open(p, 'rb').read()).hexdigest() # Kill only processes left with a cwd under this row's TMPDIR. def sweep(): left = [] for pid in filter(str.isdigit, os.listdir('/proc')): try: if os.readlink(f'/proc/{pid}/cwd').startswith(TMP + '/'): left.append(int(pid)); os.kill(int(pid), signal.SIGKILL) except OSError: pass return left os.chdir(WT) env = dict(os.environ, TMPDIR=TMP, NO_COLOR='1') base = sha(F) os.makedirs(os.path.join(PKT, 'mutants'), exist_ok=True) log = open(os.path.join(PKT, 'mutants-run.log'), 'a') for name, drop in M: src = open(F).read() if src.count(drop) != 1: print(f'{name}: pattern not found once, skipped', file=log, flush=True); print(name, 'BAD PATTERN'); continue shutil.copy2(F, F + '.orig') try: open(F, 'w').write(src.replace(drop, '')) p = subprocess.Popen('node --test tests/*.test.mjs', shell=True, cwd='packages/cli', stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env, start_new_session=True, text=True) try: res, _ = p.communicate(timeout=900) except subprocess.TimeoutExpired: os.killpg(p.pid, signal.SIGKILL); res, _ = p.communicate(); res += '\nTIMEOUT\n' open(os.path.join(PKT, 'mutants', f'mutant-{name}.log'), 'w').write(res) fails = sorted({l.strip()[2:] for l in res.splitlines() if l.strip().startswith('✖ ') and l.strip() != '✖ failing tests:'}) tally = dict(re.findall(r'^ℹ (pass|fail) (\d+)$', res, re.M)) verdict = 'killed' if p.returncode != 0 else 'SURVIVED' print(f'{name}: {verdict} (rc {p.returncode}, pass {tally.get("pass", "?")}, fail {tally.get("fail", "?")})', file=log, flush=True) for l in fails: print(f' fail: {l}', file=log, flush=True) left = sweep() if left: print(f' swept {len(left)} leftover process(es) under TMPDIR: {left}', file=log, flush=True) print(name, verdict, tally, flush=True) finally: shutil.move(F + '.orig', F) assert sha(F) == base, f'{F} not restored' print('restored:', sha(F) == base, file=log, flush=True)