# Row 77 mutants (rounds 1 and 2): each edit applied alone to , the service and pinned-copy # tests run, the file restored. Usage: python3 mutants.py import subprocess, shutil, sys W=sys.argv[1] # the tree with the candidate applied S='scripts/webui-service.sh' M=[ ('failed counts as running', S, '&& $3 != "failed" {', '{'), ('reloading not running', S, '$3 != "failed" {', '$3 != "failed" && $3 != "reloading" {'), ('list-units failure ignored', S, '2>/dev/null)" || die "refusing: systemctl --user list-units', '2>/dev/null)" || true; : "refusing: systemctl --user list-units'), ('install skips state read', S, ' state="$(running)" || exit 1\n [ -z "$state" ] || die "refusing: $UNIT_NAME is running', ' state=""\n [ -z "$state" ] || die "refusing: $UNIT_NAME is running'), ('uninstall skips state read with --no-reload', S, '[ -z "$state" ] || die "refusing: $UNIT_NAME is still running', '[ -z "$state" ] || [ "$RELOAD" = 0 ] || die "refusing: $UNIT_NAME is still running'), ('uid header case-sensitive', S, '"${AUTH_HEADER,,}" = x-authentik-uid', '"$AUTH_HEADER" = X-authentik-uid'), ('uid allows upper hex', S, '^[0-9a-f]{64}$ ]]; then', '^[0-9a-fA-F]{64}$ ]]; then'), ('allowed user may start with -', S, 'case "$ALLOW_USER" in [A-Za-z0-9]*) ;;', 'case "$ALLOW_USER" in *) ;;'), ('no --checkout in unit', S, 'flags="--checkout $REPO $flags"', 'flags="$flags"'), ('canonical always passes', S, '[ -d "$REPO/.git" ] || [ "$ALLOW_WORKTREE" = 1 ] ||', 'true ||'), ('install without --copy', S, '[ -n "$COPY_ID" ] || die "install needs --copy ID', 'true || die "install needs --copy ID'), ('missing copy accepted', S, '[ -f "$COPIES/$COPY_ID/packages/webui/src/cli.mjs" ] ||', 'true ||'), ('status drift wrong', S, 'if [ "$head" = "$copy" ]; then', 'if true; then'), ('copy misses seat', S, ' packages/seat/src\n', ''), ('copy misses README', S, ' packages/webui/README.md\n', ''), ('commit may start with a dot', S, 'case "$commit" in [A-Za-z0-9]*) ;;', 'case "$commit" in [.A-Za-z0-9]*) ;;'), ('copy misses tasks src', S, ' packages/tasks/src\n', ''), ('copy misses fields table', S, ' packages/tasks/fields.json\n', ''), ('copy stays writable', S, '-type f -exec chmod a-w', '-type f -exec true'), ('copy dirs stay writable', S, '-type d -exec chmod 0555', '-type d -exec true'), ('copy subdirs stay writable', S, '-type d -exec chmod 0555 {} +', '-maxdepth 0 -type d -exec chmod 0555 {} +'), ('trap leaves read-only temp', S, "trap 'chmod -R u+w \"$COPY_TMP\" 2>/dev/null; rm -rf", "trap 'rm -rf"), ('copy off-branch accepted', S, 'merge-base --is-ancestor "$id" refs/heads/refactor ||', 'true ||'), ('queue reads root', 'packages/webui/src/reads.mjs', 'root: queueRoot', 'root'), ('--checkout relative ok', 'packages/webui/src/cli.mjs', 'isAbsolute(', '!!('), ('install skips bus check', S, ' unit="$(render)" || exit 1\n bus_on_copy', ' unit="$(render)" || exit 1\n : bus_on_copy'), ('bus path prefix only', S, 'in "$copy"/*) ;;', 'in "$copy"*) ;;'), ('bus not-found accepted', S, '[ "$load" = loaded ] ||', 'true ||'), ('bus show failure ignored', S, '-p LoadState -p ExecStart 2>/dev/null)" || die', '-p LoadState -p ExecStart 2>/dev/null)" || true; : die'), ('bus list failure ignored', S, "'mosaic-bus@*.service' 2>/dev/null)\" || die", "'mosaic-bus@*.service' 2>/dev/null)\" || true; : die"), ('no loaded bus accepted', S, '[ -n "$units" ] || die', 'true || die'), ('no ExecStart path accepted', S, '[ -n "$paths" ] || die', 'true || die'), ('only first bus unit checked', S, ' for unit in $units; do', ' for unit in ${units%%[[:space:]]*}; do'), ('--business ignored for bus', S, ' if [ -n "$BUSINESS" ]; then\n units=', ' if false; then\n units='), ] res=[] for name,f,a,b in M: p=f'{W}/{f}'; s=open(p).read() if s.count(a)<1: res.append(f'NOMATCH {name}'); continue shutil.copy(p,p+'.bak'); open(p,'w').write(s.replace(a,b)) try: r=subprocess.run(['node','--test','tests/service.test.mjs','tests/pinned-copy.test.mjs'],cwd=f'{W}/packages/webui',capture_output=True,text=True,timeout=300) fails=[l for l in r.stdout.splitlines() if l.startswith('✖') and 'failing tests' not in l] res.append(f"{'KILLED' if r.returncode else 'SURVIVED'} {name}: {len(set(fails))} failing") finally: shutil.move(p+'.bak',p) print('\n'.join(res))