swayfx-mosaic/tooling/probes/step20c-saved-input-gpu-batch/runtime.py

79 lines
5.4 KiB
Python

"""Derived EXPERIMENTAL Session, preserving sealed historical engine bytes."""
import importlib.util,sys,os,re,time,shutil,signal,subprocess
from pathlib import Path
import ledger
from plans import HIST,BASE
# WHY CONTRACT INVARIANT: historical imports have their own plans/gate names;
# load them in a temporary namespace and restore this campaign's modules.
def historical(name):
saved={k:sys.modules.get(k) for k in ('plans','gate','engine','validators')};old=sys.path[:]
try:
for k in saved:sys.modules.pop(k,None)
sys.path[:0]=[str(HIST),str(BASE)]
spec=importlib.util.spec_from_file_location('historical_'+name,HIST/(name+'.py'));m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m);return m
finally:
sys.path[:]=old
for k,v in saved.items():
sys.modules.pop(k,None)
if v is not None:sys.modules[k]=v
engine=historical('engine')
cases=historical('cases')
SessionBase=engine.Session
class Session(SessionBase):
def debit(self,*kinds):ledger.action_debit(self.lab,self.spec['id'],*kinds)
def spawn(self,name,argv,debug=False):
argv=list(map(str,argv))
if name=='compositor':argv=[a.replace('txn-timeout=2500','txn-timeout=5000') for a in argv]
p=super().spawn(name,argv,debug)
self.persist_identity('spawn');return p
def persist_identity(self,phase):
ledger.durable(self.run/'owned-runtime.json',dict(phase=phase,uid=os.getuid(),runtime=str(self.runtime),runtime_mode=oct(self.runtime.stat().st_mode&0o777) if self.runtime.exists() else None,socket=str(getattr(self,'sock','')),processes=[dict(name=n,pid=p.pid,exit=p.poll()) for n,p in self.proc.items()],ns=time.monotonic_ns()))
def start(self):
if self.spec['variant']=='C3':self.env['SAVED_DIAGNOSTIC']='1'
super().start();self.persist_identity('socket-bound')
def inject(self,action,point,grab=False):
# No settling sleep: mutation during delivery is the measured phenomenon.
engine.need(action in ('move','left','right'),'action');outs=self.ipc('-t','get_outputs');rects=[[o['rect'][k] for k in ('x','y','width','height')] for o in outs if o['active']]
engine.need(any(engine.contains(r,point) for r in rects),'inside output')
x=min(r[0] for r in rects);y=min(r[1] for r in rects);w=max(r[0]+r[2] for r in rects)-x;h=max(r[1]+r[3] for r in rects)-y
self.debit('move',*(['click'] if action!='move' else []),*(['grab'] if grab else []));sent=[point[0]-x,point[1]-y];marker=f'INJECT_DONE {action} {sent[0]},{sent[1]}';before=self.log('pointer').count(marker);start=time.monotonic_ns()
self.pointer.stdin.write(f'{action} {sent[0]} {sent[1]} {w} {h}\n');self.pointer.stdin.flush();self.wait(lambda:self.log('pointer').count(marker)>before)
self.record('injection',action=action,global_logical=point,sent=sent,logical_extent=[w,h],grab=grab,start_ns=start,end_ns=time.monotonic_ns())
def close_client(self,name,command='close_owner'):
self.client_cmd(name,command);code=self.proc[name].wait(timeout=3);engine.need(code==0,'client exit0');self.wait(lambda:self.find(name) is None)
exits=re.findall(r'EXIT[^\n]*error=(\d+)',self.log(name));engine.need(all(e=='0' for e in exits),'Wayland error0 required even with process exit0')
if not exits:self.record('client-error-observability',name=name,status='NOT OBSERVABLE; scientific cleanup NOT PROVEN')
def cleanup(self,measurement_status,failure=None):
self.persist_identity('before-cleanup')
# Record identities/exit status durably immediately before historical
# removal, including watchdog rescue; no live desktop socket is used.
original=engine.shutil.rmtree
def remove(path,*args,**kwargs):
if Path(path)==self.runtime:self.persist_identity('before-runtime-removal')
return original(path,*args,**kwargs)
engine.shutil.rmtree=remove
try:status=super().cleanup(measurement_status,failure)
finally:engine.shutil.rmtree=original
outcome=ledger.read(self.run/'outcome.json');errors={n:re.findall(r'EXIT[^\n]*error=(\d+)',self.log(n)) for n in self.proc if n not in ('compositor','pointer')}
explicit_errors=errors.get('owner',[])
clean=outcome['cleanup_pass'] and bool(explicit_errors) and not any(e!='0' for es in errors.values() for e in es)
outcome.update(scientific_cleanup='PASS' if clean else 'INCONCLUSIVE',wayland_exit_errors=errors,measurements_pass=measurement_status,runtime_exit=outcome['processes'])
if not clean:outcome['status']='INCONCLUSIVE'
ledger.durable(self.run/'outcome.json',outcome);return outcome['status']
def main():
import argparse
from plans import DEFAULT_LAB,RUNS
p=argparse.ArgumentParser();p.add_argument('run_id',choices=RUNS);p.add_argument('--lab',type=Path,default=DEFAULT_LAB);args=p.parse_args()
auth=ledger.begin(args.lab,args.run_id);s=None;status='INCONCLUSIVE';failure=None
try:
s=Session(RUNS[args.run_id],auth,args.lab);s.start()
if s.spec['campaign']=='saved':from saved import run
elif s.spec['campaign']=='input':from input import run
else:raise ValueError('conditional diagnostics require separate reviewed runner')
status=run(s)
except Exception as e:failure=f'{type(e).__name__}: {e}'
finally:
if s is not None:status=s.cleanup(status,failure)
print(status,failure or '');return 0 if status=='PASS' else 1
if __name__=='__main__':raise SystemExit(main())