#!/usr/bin/env python3 """One independent budgeted RGB/input/standard retest of the unchanged verified static compositor. WHY: resolved relocations do not demonstrate real compositor execution. CONTRACT: verify /proc executable/library identity before mapping any client. INVARIANT: owned sockets/groups only, one camera correction, durable captures. """ import argparse import json import os from pathlib import Path import re import shutil import signal import subprocess import tempfile import threading import time from PIL import Image,ImageChops from support import environment,reserve,save,sha from pixels import analyze, compare, interior, painted_geometry, click_points, delivery from ipc_predicates import (nodes, workspace, validate_outputs, validate_reference, validate_preflight, measure_owner, scene_from_log, client_commit, validate_camera) from verify import verify_inventory p=argparse.ArgumentParser();p.add_argument('lab',type=Path);a=p.parse_args();lab=a.lab.resolve() assert not __import__('sys').flags.optimize, 'Python assertions must be enabled' assert json.loads((lab/'results/binary-identity.json').read_text())['status']=='PASS' assert sha(lab/'swayfx-mosaic')=='f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b' assert (Path.home()/'swayfx-mosaic-static-rgb-input-retest-v1.owner').read_text().strip()==str(lab) # CONTRACT: run reservation is impossible until the exact frozen fixture passes. assert not __import__('sys').flags.optimize, 'Python assertions must be enabled' verify_inventory(lab/'results/fixture-freeze-sha256.txt') gate=json.loads((lab/'results/offline-gate.json').read_text()) assert gate=={'reference':'PASS','preflight':'PASS','AST':'PASS','ledger':'PASS','source_audit':'PASS'} assert json.loads((lab/'results/ipc-replay.json').read_text())['marker']=='STATIC_REFERENCE_IPC_REPLAY=PASS' assert json.loads((lab/'results/preflight-replay.json').read_text())['marker']=='STATIC_FIXTURE_PREFLIGHT_REPLAY=PASS' reserve(lab,'run');run=lab/'run';run.mkdir();runtime=Path(tempfile.mkdtemp(prefix='msr-',dir='/tmp')) runtime.chmod(0o700);(run/'runtime-path').write_text(str(runtime)+'\n') (run/'config').write_text('output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0\noutput HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\n') env=environment();env.update(XDG_RUNTIME_DIR=str(runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2',WLR_RENDERER='pixman') binary=lab/'swayfx-mosaic';processes=[];files=[];failure=None;compositor=None;events=[];stage='startup' started=time.monotonic();finished=threading.Event();expired=threading.Event() def event(kind,**data): row={'ns':time.monotonic_ns(),'kind':kind,**data} path=run/'events.jsonl' assert not path.exists() or path.stat().st_size<64*1024*1024, 'event output bound' with path.open('a') as out:out.write(json.dumps(row)+'\n') def stop_owned(): for proc in list(processes): try:os.killpg(proc.pid,signal.SIGTERM) except ProcessLookupError:pass def watchdog(): while not finished.wait(.1): if time.monotonic()-started>150 or any(x.stat().st_size>64*1024*1024 for x in run.glob('*.log')): expired.set();stop_owned();return thread=threading.Thread(target=watchdog,daemon=True);thread.start() def spawn(name,argv,stdin=False,extra=None): assert not expired.is_set() out=(run/(name+'.log')).open('w');files.append(out) proc=subprocess.Popen(list(map(str,argv)),env={**env,**(extra or {})},stdin=subprocess.PIPE if stdin else subprocess.DEVNULL,stdout=out,stderr=subprocess.STDOUT,text=True,start_new_session=True) processes.append(proc);event('spawn',name=name,pid=proc.pid,argv=list(map(str,argv)));return proc def wait(predicate,seconds=8): end=time.monotonic()+seconds while time.monotonic()before) time.sleep(.15);event('pointer',action=action,global_point=[x,y],target_tail=log('target').splitlines()[-6:] if (run/'target.log').exists() else []) def capture(tag,output): reserve(lab,'capture');path=run/(tag+'.png') subprocess.run(['grim','-s','1','-o',output,str(path)],env=env,check=True,timeout=5,stdout=subprocess.DEVNULL) im=Image.open(path).convert('RGB');assert im.size==(800,600) event('capture',file=path.name,output=output,sha256=sha(path));return im def measure(owner):return measure_owner(log('compositor'),owner) # WHY: global focus becomes H2 after the forbidden-region click. Workspace and # owner IDs, rather than global command context, bind the standard-return test. focus_states=[] def snapshot(tag): tree=ipc('-t','get_tree');outputs=ipc('-t','get_outputs');workspaces=ipc('-t','get_workspaces') row={'tag':tag,'ns':time.monotonic_ns(),'tree':tree,'outputs':outputs,'workspaces':workspaces, 'focused':[n['id'] for n in nodes(tree) if n.get('focused')]} focus_states.append(row);save(lab,'focus-states',focus_states) save(lab,tag+'-snapshot',row) return tree def scene(output):return scene_from_log(log('compositor'),output) def camera(tree):return workspace(tree,'1')['mosaic']['rows'][0]['scroll_x'] def buttons(name):return [x for x in log(name).splitlines() if x.startswith('BUTTON ')] def measured_click(kind,point): before={n:len(buttons(n)) for n in ['target','reference','filler']} inject('right',*point,kind=kind) row={'global_point':point,'events':{n:buttons(n)[before[n]:] for n in before}, 'tails':{n:log(n).splitlines()[-15:] for n in before},'ns':time.monotonic_ns()} save(lab,kind,row);return row def whole(tag,baseline,im): value=compare(baseline,im);save(lab,tag,value);assert value['equal'],'whole H2 differs: '+tag return value try: compositor=spawn('compositor',[binary,'-d','-c',run/'config']) sock=wait(lambda:next(iter(runtime.glob('sway-ipc.*.sock')),None)) display=wait(lambda:next((x for x in runtime.glob('wayland-*') if x.is_socket()),None)) env.update(SWAYSOCK=str(sock),WAYLAND_DISPLAY=display.name) maps=Path(f'/proc/{compositor.pid}/maps').read_text();(run/'proc-maps.txt').write_text(maps) exe=Path(f'/proc/{compositor.pid}/exe').resolve();assert exe==binary and sha(exe)==sha(binary) paths=sorted({line.split()[-1] for line in maps.splitlines() if len(line.split())>=6 and line.split()[-1].startswith('/')}) wl=Path('/usr/lib/libwlroots-0.20.so').resolve() assert not any('libscenefx' in x for x in paths) assert [Path(x).resolve() for x in paths if 'libwlroots' in x]==[wl] assert sha(wl)=='7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837' assert not any('/swayfx-mosaic-' in x and Path(x)!=binary for x in paths) save(lab,'dependency-mappings',{'status':'PASS','exe':str(exe),'paths':paths,'wlroots_sha256':sha(wl),'SceneFX_shared':False}) outputs=ipc('-t','get_outputs');validate_outputs(outputs);actual={o['name']:(o['rect']['x'],o['rect']['y'],o['rect']['width'],o['rect']['height'],o['scale'],o['transform']) for o in outputs if o['active']} assert actual=={'HEADLESS-1':(-800,0,800,600,1.0,'normal'),'HEADLESS-2':(0,0,800,600,1.0,'normal')},actual save(lab,'output-geometry',outputs) pointer=spawn('pointer',[lab/'pointer-daemon','1600','600'],stdin=True);wait(lambda:'POINTER_READY' in log('pointer')) stage='clients' cmd('workspace 2');ref=spawn('reference',[lab/'static-reference'],stdin=True,extra={'WAYLAND_DEBUG':'1'}) reference=wait(lambda:find('org.mosaic.StaticReference'));refid=reference['id'] cmd(f'[con_id={refid}] border none') cmd('workspace 1');filler=spawn('filler',[lab/'static-reference'],stdin=True) wait(lambda:len([n for n in nodes(ipc('-t','get_tree')) if n.get('app_id')=='org.mosaic.StaticReference'])==2) target=spawn('target',[lab/'saved-client'],stdin=True,extra={'WAYLAND_DEBUG':'1'}) wait(lambda:find('org.mosaic.SavedBufferProbe')) stage='preflight' cmd('layout mosaic');time.sleep(.6) tree=snapshot('before-camera');owner=find('org.mosaic.SavedBufferProbe');targetid=owner['id'];wsid=workspace(tree,'1')['id'] before=measure(owner);sc_before=scene('HEADLESS-1');scroll_before=camera(tree) assert sc_before['scroll']==scroll_before x,w=before['horizontal_content'];desired=-w//2;delta=x-desired reserve(lab,'camera');cmd(f'layout mosaic scroll {delta}') time.sleep(.6);owner=find('org.mosaic.SavedBufferProbe');after=measure(owner) tree=snapshot('preflight');scroll_after=camera(tree);sc_after=scene('HEADLESS-1') save(lab,'camera-calibration',{'before':scroll_before,'delta':delta,'desired_left':desired,'after':scroll_after,'before_scene':sc_before,'after_scene':sc_after}) validate_camera(scroll_before,scroll_after,delta,sc_after) x,w=after['horizontal_content'];assert x<00 and forbidden['count']==0 whole('v1-H2-comparison',baseline,right) paint=painted_geometry(left,after,commit) points=click_points(left,right,after) permitted=points['permitted'];forbidden_point=points['forbidden'] save(lab,'painted-geometry',{'paint':paint,'measured':after,'commit':commit,'points':points}) save(lab,'pixel-analysis',{'STATIC_MOSAIC_VISIBLE_PIXELS':'PASS', 'STATIC_MOSAIC_FORBIDDEN_PIXELS':forbidden['count'],'STATIC_MOSAIC_H2_WHOLE_RGB':'PASS', 'H1':visible,'H2':forbidden}) save(lab,'click-points',{'permitted':permitted,'forbidden':forbidden_point,'paint_bbox':visible['bbox'],'horizontal_content':[x,w]}) stage='visible-input' click=measured_click('visible_click',permitted);events_visible=delivery(click['events'],'target') assert not click['events']['reference'] and not click['events']['filler'] save(lab,'visible-input',{'status':'PASS','point':permitted,'events':events_visible}) tree=snapshot('after-visible');assert camera(tree)==scroll_after assert measure(find('org.mosaic.SavedBufferProbe'))['horizontal_content']==[x,w] stage='forbidden-input' click=measured_click('forbidden_click',forbidden_point);events_reference=delivery(click['events'],'reference') assert not click['events']['target'] and not click['events']['filler'],'invisible target or wrong client input' save(lab,'forbidden-input',{'status':'PASS','target_received':False}) save(lab,'reference-input',{'status':'PASS','point':forbidden_point,'events':events_reference,'invisible_target_events':click['events']['target']}) tree=snapshot('after-reference');assert camera(tree)==scroll_after assert next(n for n in nodes(tree) if n['id']==refid)['focused'] save(lab,'reference-after-focus',validate_reference(focus_states[-1],refid,ref.pid)) inject('move',-790,590);time.sleep(.6) whole('post-click-H2-comparison',baseline,capture('h2-after-clicks','HEADLESS-2')) stage='standard-return' cmd(f'[con_id={targetid}] layout splith');time.sleep(.6) tree=snapshot('standard');standard_ws=workspace(tree,'1');assert standard_ws['id']==wsid and standard_ws['layout']=='splith' standard_owner=next(n for n in nodes(standard_ws) if n['id']==targetid);assert standard_owner['visible'] standard_left=capture('h1-standard','HEADLESS-1');standard_right=capture('h2-standard','HEADLESS-2') save(lab,'standard-pixels',{'H1':analyze(standard_left),'H2':analyze(standard_right)}) assert analyze(standard_right)['count']==0, 'standard target leaked to H2' whole('standard-H2-comparison',baseline,standard_right) standard_scene=scene('HEADLESS-1');standard_h2=scene('HEADLESS-2') assert standard_scene['workspace']==wsid and not standard_scene['clip_valid'] and not standard_h2['clip_valid'] sx,sy=interior(standard_left);point=(sx-800,sy) click=measured_click('standard_click',point);events_standard=delivery(click['events'],'target') assert not click['events']['reference'] and not click['events']['filler'] final_tree=snapshot('after-standard-click') assert next(n for n in nodes(final_tree) if n['id']==targetid)['focused'] assert [z['name'] for z in focus_states[-1]['workspaces'] if z['focused']]==['1'] save(lab,'reference-after-standard',validate_reference(focus_states[-1],refid,ref.pid)) inject('move',-790,590);time.sleep(.6) whole('final-H2-comparison',baseline,capture('h2-standard-after-click','HEADLESS-2')) save(lab,'standard-return',{'status':'STATIC_STANDARD_RETURN=PASS','workspace':standard_ws,'owner':measure(standard_owner),'scene':standard_scene,'H2_scene':standard_h2,'point':point,'events':events_standard,'pixels':analyze(standard_left),'H2_equal':True}) assert all(p.poll() is None for p in [target,ref,filler,pointer]) stage='completed' cmd('exit');compositor.wait(timeout=5);assert compositor.returncode==0 except Exception as exc: failure=f'{type(exc).__name__}: {exc}';event('failure',message=failure) finally: finished.set();thread.join(timeout=1);stop_owned() for proc in processes: try:proc.wait(timeout=3) except subprocess.TimeoutExpired: os.killpg(proc.pid,signal.SIGKILL);proc.wait(timeout=3) try:os.killpg(proc.pid,signal.SIGKILL) except ProcessLookupError:pass for out in files:out.close() shutil.rmtree(runtime) save(lab,'runtime',{'status':'PASS' if failure is None else 'INCONCLUSIVE','failure':failure,'stopping_stage':stage,'duration_seconds':time.monotonic()-started,'process_exit_codes':[{'pid':x.pid,'argv':x.args,'exit':x.returncode} for x in processes],'runtime_removed':str(runtime),'cleanup':'owned groups reaped, runtime removed'}) damage=[s for s in log('compositor').splitlines() if 'TEMPORAL_DAMAGE ' in s] if (run/'compositor.log').exists() else [] save(lab,'damage-analysis',{'extents':damage,'all_frames':'NOT TESTED','PNG_native_buffer_identity':'NOT PROVEN','client_damage':'wl_surface_damage_buffer full SHM at actual commits; see client source/protocol logs'}) print('STATIC_RGB_INPUT_STANDARD',failure or 'PASS') if failure:raise SystemExit(1)