69 lines
4.3 KiB
Python
69 lines
4.3 KiB
Python
"""Filbert's row 63 round 2 verification. Run only after the booked test slot clears."""
|
||
import os, subprocess, signal, pathlib, json, hashlib
|
||
root=pathlib.Path(os.environ.get('MOSAIC_REVIEW_ROOT',str(pathlib.Path.home()/'filbert-scratch/r63-review/wt')))
|
||
out=root.parent/'verification'; out.mkdir(exist_ok=True)
|
||
env=dict(os.environ,TMPDIR=str(pathlib.Path.home()/'filbert-scratch/tmp'),DOCKER_HOST='unix:///nonexistent.sock')
|
||
# Do not route around another seat's test ownership.
|
||
seats=['darkwing','dewey','rocko','researcher','sage']
|
||
other=[str(pathlib.Path.home()/f'{seat}-scratch') for seat in seats]
|
||
other += [str(pathlib.Path('/mnt/storage/scratch')/seat) for seat in seats]
|
||
def require_quiet():
|
||
busy=[]
|
||
for p in pathlib.Path('/proc').iterdir():
|
||
if not p.name.isdigit(): continue
|
||
try:
|
||
cwd=str((p/'cwd').resolve(strict=True))
|
||
if any(cwd==x or cwd.startswith(x+'/') for x in other):busy.append((p.name,cwd))
|
||
except (OSError,RuntimeError):pass
|
||
if busy:raise SystemExit('Other scratch processes still active: '+repr(busy))
|
||
require_quiet()
|
||
manifest=pathlib.Path('/mnt/storage/src/mosaic-stack/agents/darkwing/work/queue-63/r2/candidate-manifest.sha256')
|
||
def matches():
|
||
for line in manifest.read_text().splitlines():
|
||
sha,path=line.split();assert hashlib.sha256((root/path).read_bytes()).hexdigest()==sha,path
|
||
matches()
|
||
def run(cmd,cwd,log):
|
||
with log.open('w') as f:
|
||
p=subprocess.Popen(cmd,cwd=cwd,env=env,stdout=f,stderr=subprocess.STDOUT,start_new_session=True)
|
||
try:p.wait(timeout=120)
|
||
except subprocess.TimeoutExpired:
|
||
os.killpg(p.pid,signal.SIGKILL);p.wait();return 'timeout'
|
||
return p.returncode
|
||
rows=[]
|
||
for package in ['tasks','harness']:
|
||
cwd=root/'packages'/package
|
||
files=[str(p.relative_to(cwd)) for p in sorted((cwd/'tests').glob('*.test.mjs'))]
|
||
log=out/f'{package}.log';rc=run(['node','--test',*files],cwd,log)
|
||
stats=[s for s in log.read_text().splitlines() if s.startswith(('ℹ tests ','ℹ pass ','ℹ fail ','# tests ','# pass ','# fail '))]
|
||
row={'suite':package,'rc':rc,'stats':stats};rows.append(row);print(json.dumps(row),flush=True)
|
||
if rc!=0:(out/'summary.json').write_text(json.dumps(rows,indent=2)+'\n');raise SystemExit('Baseline suite failed')
|
||
R='{ ...at, project: ctx.project, view: ctx.view }'
|
||
mutants=[
|
||
('located-first-only','for (const r of list) {','for (const r of list.slice(0, 1)) {'),
|
||
('step-reread-root','after = await read(at);\n } catch {\n // the re-read failed too',f'after = await read({R});\n }} catch {{\n // the re-read failed too'),
|
||
('handle-final-root','after = await read(at);\n } catch {\n // handled below: a failed',f'after = await read({R});\n }} catch {{\n // handled below: a failed'),
|
||
('create-final-root','after = await read(at);\n } catch {\n // handled below, as in handle()',f'after = await read({R});\n }} catch {{\n // handled below, as in handle()'),
|
||
('create-fallback-root-todo','taskFields(r.json, at.view.ids.todo)','taskFields(r.json, ctx.view.ids.todo)'),
|
||
('read-done-root','let bucket = at.view.done;','let bucket = ctx.view.done;'),
|
||
('close-done-root','(a) => a.task.done && a.bucket === view.done,','(a) => a.task.done && a.bucket === ctx.view.done,'),
|
||
]
|
||
pattern='a verb and a create in the child|a create in the child whose final read fails|a lost answer in the child|every related task is read before authority'
|
||
p=root/'packages/tasks/src/verbs.mjs'
|
||
for name,old,new in mutants:
|
||
require_quiet()
|
||
src=p.read_text();assert src.count(old)==1,(name,src.count(old))
|
||
try:
|
||
p.write_text(src.replace(old,new));log=out/f'mutant-{name}.log'
|
||
rc=run(['node','--test','--test-name-pattern='+pattern,'packages/tasks/tests/verbs.test.mjs'],root,log)
|
||
failures=[s for s in log.read_text().splitlines() if s.startswith(('✖','not ok'))]
|
||
row={'mutant':name,'rc':rc,'result':'survived' if rc==0 else 'timeout' if rc=='timeout' else 'killed','failures':failures[:4]}
|
||
rows.append(row);print(json.dumps(row),flush=True)
|
||
finally:p.write_text(src)
|
||
matches()
|
||
# Fresh passing run after restoration of all mutations.
|
||
rc=run(['node','--test','--test-name-pattern='+pattern,'packages/tasks/tests/verbs.test.mjs'],root,out/'restored-proof-tests.log')
|
||
rows.append({'restored_proof_tests_rc':rc,'manifest_matches':True})
|
||
(out/'summary.json').write_text(json.dumps(rows,indent=2)+'\n')
|
||
print(json.dumps(rows[-1]),flush=True)
|
||
raise SystemExit(rc!=0 or any(r.get('result') in ['survived','timeout'] for r in rows))
|