Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
51 lines
2.8 KiB
Python
51 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
# Running cases requires Sage's separate full test grant. --preflight is read-only.
|
|
import json, os, pathlib, signal, subprocess, sys, re
|
|
ROOT=pathlib.Path('/mnt/storage/src/mosaic-stack-rocko-171')
|
|
OUT=pathlib.Path(__file__).resolve().parent/'mutants'
|
|
cases=json.loads((OUT.parent/'mutant-sites.json').read_text())
|
|
for c in cases:
|
|
assert (ROOT / c['path']).read_text().count(c['old']) == 1, c['check']
|
|
if '--preflight' in sys.argv:
|
|
print('PREFLIGHT_OK', len(cases)); sys.exit(0)
|
|
OUT.mkdir(exist_ok=True)
|
|
resume = next((int(a.split('=')[1]) for a in sys.argv if a.startswith('--resume=')), 1)
|
|
rows = json.loads((OUT / 'results.json').read_text())[:resume - 1] if resume > 1 else []
|
|
def stop_tree(pid):
|
|
# Git's own runner creates process groups too. Capture descendants before killing the parent.
|
|
descendants = {pid}
|
|
for _ in range(8):
|
|
before = set(descendants)
|
|
for entry in pathlib.Path('/proc').iterdir():
|
|
if not entry.name.isdigit(): continue
|
|
try:
|
|
fields = (entry / 'stat').read_text().split(') ', 1)[1].split()
|
|
if int(fields[1]) in descendants: descendants.add(int(entry.name))
|
|
except (OSError, ValueError, IndexError): pass
|
|
if before == descendants: break
|
|
for child in descendants:
|
|
try: os.killpg(child, signal.SIGKILL)
|
|
except ProcessLookupError: pass
|
|
for c in cases[resume - 1:]:
|
|
file = ROOT / c['path']; original = file.read_bytes(); process = None
|
|
try:
|
|
file.write_text(original.decode().replace(c['old'], c['new']))
|
|
with (OUT / (c['check'] + '.log')).open('w') as log:
|
|
process = subprocess.Popen(['node', '--test', '--test-name-pattern', re.escape(c['killer']), c['test']], cwd=ROOT,
|
|
env={**os.environ, 'DOCKER_HOST': 'unix:///nonexistent.sock', 'TMPDIR': '/mnt/storage/scratch/rocko-171/tmp', 'MOSAIC_CONFIG': '/mnt/storage/scratch/rocko-171/no-live-config.json'}, stdout=log,
|
|
stderr=subprocess.STDOUT, start_new_session=True)
|
|
try: rc = process.wait(timeout=180)
|
|
except subprocess.TimeoutExpired: stop_tree(process.pid); process.wait(); rc = 'timeout'
|
|
content = (OUT / (c['check'] + '.log')).read_text()
|
|
checker = ['node', '--check', c['path']] if c['path'].endswith(('.mjs', '.js')) else ['bash', '-n', c['path']]
|
|
syntax = subprocess.run(checker, cwd=ROOT, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, start_new_session=True, timeout=10)
|
|
invalid = syntax.returncode != 0 or 'ERR_MODULE_NOT_FOUND' in content
|
|
verdict = 'INVALID' if invalid else 'TIMEOUT' if rc == 'timeout' else 'SURVIVED' if rc == 0 else 'KILLED'
|
|
rows.append({k: c[k] for k in ['check', 'path', 'test', 'killer']} | {'result': verdict, 'exit': rc})
|
|
(OUT / 'results.json').write_text(json.dumps(rows, indent=2) + '\n')
|
|
print(c['check'], verdict, rc, flush=True)
|
|
finally:
|
|
if process is not None and process.poll() is None: stop_tree(process.pid); process.wait()
|
|
file.write_bytes(original)
|
|
if verdict != 'KILLED': sys.exit(1)
|