source_export_cache.py (5817B)
1 #!/usr/bin/env python3 2 """Opt-in export-cache traversal: authenticated candidates and scripted IO.""" 3 from pathlib import Path 4 from compiler_candidate import Candidate,options,ROOT 5 from source_resolution_models import n,LEAF,pair,tree_list,result,expect,chars 6 from source_modules import tree_write 7 from http_pipeline import action,script,finished 8 from native_library_migration import digest 9 DRIVER='''!import prelude as B 10 !import source_export_cache as C 11 !import effects as E 12 !export build 13 !export all 14 !export select 15 !export wrong 16 build source replies = E.run (C.sourceExportCacheBuild "cas" source "compiler") replies 17 all entries selected replies = E.run (C.sourceExportCacheAll entries "cas" "scope" selected) replies 18 select requests definitions selected replies = E.run (C.sourceExportCacheSelect requests "cas" "scope" definitions selected) replies 19 wrong _ = t 20 ''' 21 22 def cached(name,tree,pin,fail=False): 23 namehash=digest('arboricx.source-export-name.v1',name.encode());path='scope/'+namehash 24 trace=[action(28,chars('arboricx.source-export-name.v1\0'+name)),action(10,chars('export-start='+name+'\n')),action(27,chars(path)),action(20,chars(path)),action(50,pair(chars('cas/objects/'+pin[:3]+'/'+pin),chars(pin)))] 25 failed=result(False,chars('corrupt cached tree'),n(99)) 26 replies=[(28,result(True,chars(namehash))),(10,LEAF),(27,result(True,b'\1\0')),(20,result(True,chars(pin))),(50,failed if fail else result(True,tree))] 27 if not fail:trace.append(action(10,chars('export-reused='+name+'\n')));replies.append((10,LEAF)) 28 return trace,replies 29 30 def cases(): 31 # Source is scalar text; cache identities hash its explicit UTF-8 bytes. 32 failure = result(False, chars('stop after key bytes'), n(99)) 33 for label, text in [('ascii-key', 'main = 23\n'), ('utf8-key', 'main = "é€😀e\u0301"\n')]: 34 encoded = tree_list(n(byte) for byte in text.encode('utf-8')) 35 preimage = tree_list(n(byte) for byte in b'arboricx.source-export-build.v1\0compiler\0' + text.encode('utf-8')) 36 replies = script([(38, result(True, encoded)), (28, failure)]) 37 yield 'build', label, [chars(text), replies], finished(failure, [action(38, chars(text)), action(28, preimage)]) 38 invalid = tree_list([n(0xd800)]) 39 yield 'build', 'invalid-scalar', [invalid, script([(38, failure)])], finished(failure, [action(38, invalid)]) 40 pa,pb='a'*64,'b'*64;x,y=pair(chars('x'),n(23)),pair(chars('y'),n(24));xp,yp=pair(chars('x'),chars(pa)),pair(chars('y'),chars(pb)) 41 tx,rx=cached('x',n(23),pa);ty,ry=cached('y',n(24),pb) 42 yield 'all','empty',[LEAF,tree_list([xp]),LEAF],finished(result(True,tree_list([xp]))) 43 yield 'all','two-cached',[tree_list([x,y]),LEAF,script(rx+ry)],finished(result(True,tree_list([yp,xp])),tx+ty) 44 yield 'all','duplicate-first',[tree_list([x]),tree_list([xp]),LEAF],finished(result(False,chars('duplicate export'))) 45 yield 'all','duplicate-later',[tree_list([x,y]),tree_list([yp]),script(rx)],finished(result(False,chars('duplicate export')),tx) 46 trace,replies=cached('x',n(23),pa,True) 47 yield 'all','corrupt-cache',[tree_list([x,y]),LEAF,script(replies)],finished(result(False,chars('corrupt cached tree'),n(99)),trace) 48 namehash=digest('arboricx.source-export-name.v1',b'x');path='scope/'+namehash;trace,replies=tree_write(n(23),pa) 49 trace=[action(28,chars('arboricx.source-export-name.v1\0x')),action(10,chars('export-start=x\n')),action(27,chars(path))]+trace+[action(33,pair(chars(path),chars(pa))),action(10,chars('export-complete=x\t'+pa+'\n'))] 50 replies=[(28,result(True,chars(namehash))),(10,LEAF),(27,result(True,LEAF))]+replies+[(33,result(True,LEAF)),(10,LEAF)] 51 yield 'all','new-entry',[tree_list([x]),LEAF,script(replies)],finished(result(True,tree_list([xp])),trace) 52 yield 'select','empty',[LEAF,LEAF,LEAF,LEAF],finished(result(False,chars('module requires an export'))) 53 yield 'select','missing',[tree_list([tree_list([chars('absent')])]),LEAF,LEAF,LEAF],finished(result(False,chars('undeclared local export'))) 54 yield 'select','duplicate',[tree_list([tree_list([chars('x')])]),tree_list([x]),tree_list([xp]),LEAF],finished(result(False,chars('duplicate export'))) 55 # A completed first request feeds the next selection; failure stops before 56 # publishing a manifest and leaves the completed cache entry intact. 57 yield 'select','first-before-missing',[tree_list([tree_list([chars('x')]),tree_list([chars('absent')])]),tree_list([x]),LEAF,script(rx)],finished(result(False,chars('undeclared local export')),tx) 58 yield 'select','star-before-missing',[tree_list([tree_list([chars('*')]),tree_list([chars('absent')])]),tree_list([x,y]),LEAF,script(rx+ry)],finished(result(False,chars('undeclared local export')),tx+ty) 59 60 def main(): 61 args=options(__doc__).parse_args();c=Candidate(args,'source_build');r=c.runner 62 (c.root/'harness.py').write_bytes(Path(__file__).read_bytes()) 63 try: 64 (c.workspace/'source_export_cache.tri').write_bytes((ROOT/'lib/source/export-cache.tri').read_bytes()) 65 (c.workspace/'effects.tri').write_bytes((ROOT/'test/httpEffects.tri').read_bytes()) 66 (c.workspace/'export_probe.tri').write_text(DRIVER);pin=r.imported(c.build('export_probe'),'probe');c.audit() 67 rows=list(cases()) 68 for name,label,arguments,wanted in rows:expect(c.invoke(pin,name,arguments,label=label),wanted,label) 69 assert c.invoke(pin,'wrong',[LEAF])!=result(True,LEAF) 70 assert (c.workspace/'source_export_cache.tri').read_bytes()==(ROOT/'lib/source/export-cache.tri').read_bytes() 71 c.report.update(complete=True,effect_cases=len(rows),wrong_subject_rejected=True) 72 except Exception as error:c.report['failure']=str(error);raise 73 finally:c.save() 74 print('PASS export cache',len(rows),c.report['seconds']) 75 76 if __name__=='__main__':main()