"""Shared offline/live predicates: facts precede scientific classification. CONTRACT: full RGB comparisons have no mask/tolerance; grabs require real logs. INVARIANT: dismissal, client destruction, clipping and layout remain distinct. """ import re from model import Rect,Space,Provenance,Output,popup,crossing,local_clip_to_global,require from pixels import color_geometry,COLORS from PIL import Image,ImageChops class Defect(RuntimeError): """A directly measured scientific contract failure, distinct from fixture ambiguity.""" def paint(im,target): im=im.convert('RGB');assert im.size==(800,600) mask=Image.new('L',im.size);mask.putdata([255 if p==COLORS[target] else 0 for p in im.get_flattened_data()]) return dict(count=sum(mask.histogram()[1:]),bbox=mask.getbbox()) def point(im,target,origin): box=paint(im,target)['bbox'];assert box,('no measured paint',target) # WHY: A may be occluded by B; bbox center alone is not a valid hit point. x1,y1,x2,y2=box for y in range(y1+10,y2-10,5): for x in range(x1+10,x2-10,5): if all(im.getpixel((xx,yy))==COLORS[target] for yy in range(y-8,y+9) for xx in range(x-8,x+9)): return [x+origin,y] raise AssertionError('no solid measured interior '+target) def equal(a,b): assert a.size==b.size==(800,600) rows=[(i%800,i//800,x,y) for i,(x,y) in enumerate(zip(a.get_flattened_data(),b.get_flattened_data())) if x!=y] return dict(equal=not rows,changed=len(rows),bbox=ImageChops.difference(a,b).getbbox(),pixels=rows) def reference(a,b): result=equal(a,b) assert result['equal'] and all(c==(48,48,56) for c in a.get_flattened_data()),'unstable reference' return result def configured(text,name): rows=re.findall(rf'{name} ROLE_CONFIGURE x=(-?\d+) y=(-?\d+) w=(\d+) h=(\d+)',text) assert rows,(name,'no configure') return list(map(int,rows[-1])) def geometry(text,origin,provenance=None): a=configured(text,'POPUP_A');b=configured(text,'POPUP_B') require(provenance is not None,'popup-owner-provenance-required') p=provenance top=Rect(Space.GLOBAL,*origin,0,0,p) ids={n:list(map(int,re.search(rf'{n} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',text).groups())) for n in ('TOPLEVEL','POPUP_A','POPUP_B')} result={} parent=top for key,name,parent_name,c in [('A','POPUP_A','TOPLEVEL',a),('B','POPUP_B','POPUP_A',b)]: parent_id=int(re.search(rf'{name} CREATE .*parent_xdg=(\d+)',text)[1]) ack=int(re.findall(rf'{name} XDG_CONFIGURE serial=(\d+) ack',text)[-1]) parent=popup(parent,Rect(Space.POPUP,*c,p),parent_id,ids[parent_name][1],ack,name+' MAP ' in text) result[key]=parent.values() result.update(configured_A=a,configured_B=b,origin=origin,provenance=dict(owner=p.owner,workspace=p.workspace,generation=p.generation,evidence=p.evidence,state=p.state), crossing={key:crossing(Rect(Space.GLOBAL,*result[key],p),0) for key in ('A','B')}) return result def mapped(text): for name in ['POPUP_A','POPUP_B']: assert text.count(name+' MAP ')==1 and name+' POPUP_DONE ' not in text and name+' DESTROY ' not in text,(name,'not mapped') def protocol(text): assert not re.search(r'wl_display[^\n]*\.error\(|protocol error|invalid_popup_parent|not_the_topmost_popup',text,re.I),'protocol error' mapped(text) ids={} for name in ['TOPLEVEL','POPUP_A','POPUP_B']: m=re.search(rf'{name} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',text);assert m ids[name]=list(map(int,m.groups())) assert len({n for v in ids.values() for n in v})==9,'object identities reused' grabs=[] for name,parent in [('POPUP_A','TOPLEVEL'),('POPUP_B','POPUP_A')]: create=re.search(rf'{name} CREATE .*parent={parent} parent_xdg=(\d+)',text);assert create assert int(create[1])==ids[parent][1], 'wrong XDG parent' debug_parent=re.search(rf'xdg_surface#{ids[name][1]}\.get_popup\(new id xdg_popup#{ids[name][2]}, xdg_surface#{ids[parent][1]}, xdg_positioner#\d+\)',text) require(debug_parent is not None,'debug-parent-request') m=re.search(rf'{name} GRAB_REQUEST origin=wl_pointer.button target={parent} serial=(\d+) used=(\d+)',text);assert m serial,used=map(int,m.groups());assert serial==used and 0=2 assert request.start() POPUP_A -> POPUP_B',status='PASS') def pair(lines,target): events=[] for line in lines: m=re.fullmatch(r'POINTER_BUTTON target=(\w+) serial=(\d+) button=(\d+) state=(\d+)',line);assert m events.append([m[1],*map(int,m.groups()[1:])]) if len(events)!=2 or any(e[0]!=target for e in events) or [e[2:] for e in events]!=[[273,1],[273,0]]: raise Defect('wrong actual button recipient/pair: '+str(events)) assert 0