#!/usr/bin/env python3 # Row 112 (#1606): mutants on the decision.raise choice checks in broker.mjs. # m1 restores the old shared line, so a recommendation miss refuses # invalid-options again (the brief's required mutant). # m2 checks the recommendation before duplicate keys, so a bad option list # with a missing recommendation reports invalid-recommendation. # Each runs the FULL bus suite (node --test tests/*.test.mjs in packages/bus) # and restores broker.mjs from the .orig copy whatever happens. # Usage: mutants.py import os, shutil, subprocess, sys, signal WT, LOGS = sys.argv[1], sys.argv[2] SRC = os.path.join(WT, 'packages/bus/src/broker.mjs') CUR = (" if (new Set(choices).size !== choices.length) fail('invalid-options');\n" " if (!choices.includes(a.recommendation)) fail('invalid-recommendation');\n") MUTANTS = { 'm1': (" if (new Set(choices).size !== choices.length || !choices.includes(a.recommendation))\n" " fail('invalid-options');\n"), 'm2': (" if (!choices.includes(a.recommendation)) fail('invalid-recommendation');\n" " if (new Set(choices).size !== choices.length) fail('invalid-options');\n"), } os.makedirs(LOGS, exist_ok=True) text = open(SRC).read() if text.count(CUR) != 1: sys.exit('STOP the choice checks in broker.mjs are not unique or have moved') if os.path.exists(SRC + '.orig'): sys.exit('STOP broker.mjs.orig already exists') env = dict(os.environ, TMPDIR=os.path.expanduser('~/darkwing-scratch/r112/tmp')) for name, new in MUTANTS.items(): 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('STOP broker.mjs was not restored')