#!/usr/bin/env python3 # Row 115 (#1609): the brief's three mutants of the decision close message. # m1 drops the message: the close block never runs. # m2 addresses the message to route_to instead of the raising role. # m3 drops the raising-run check, so the raiser's own withdraw sends one too. # Each runs the FULL bus suite (node --test tests/*.test.mjs in packages/bus) # and restores the source from the .orig copy whatever happens. # Usage: mutants.py import os, shutil, subprocess, sys, signal WT, LOGS = sys.argv[1], sys.argv[2] MUTANTS = { 'm1': ('packages/bus/src/broker.mjs', " if (op !== 'seen' && (s.human || s.run !== d.raised_by_run)) {\n", " if (false) {\n"), 'm2': ('packages/bus/src/broker.mjs', " d.raised_by_role,\n 'DECISION',\n", " d.route_to,\n 'DECISION',\n"), 'm3': ('packages/bus/src/broker.mjs', " if (op !== 'seen' && (s.human || s.run !== d.raised_by_run)) {\n", " if (op !== 'seen') {\n"), } os.makedirs(LOGS, exist_ok=True) env = dict(os.environ, TMPDIR=os.path.expanduser('~/darkwing-scratch/r115/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/bus'), 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')