backend-resume-probe.mjs (3732B)
1 // Actual page-facing step IDs and run-bound v2 terminal replies. 2 export async function probeResume(backend){ 3 const base=new URL('../',document.querySelector('script[type=module][src]').src); 4 const api=await import(new URL('dist/probe-api.js',base)),{Terminal}=api.ioTerminal; 5 const releases=api.packages.packageIndex(await(await fetch(new URL('packages.json',base))).json()); 6 const lib=name=>api.packages.packageReleases(releases,'tricu-lib-'+name)[0].manifest; 7 const worker=new Worker(new URL('./wasm-worker.js?backend='+backend,document.querySelector('script[type=module][src]').src),{type:'module'}),events=[]; 8 worker.onmessage=e=>events.push(e.data); 9 const assert=(ok,message)=>{if(!ok)throw Error(message);},sleep=()=>new Promise(r=>setTimeout(r,40)); 10 let consumedStep=0; 11 const wait=async(predicate,start=0,stepping=false)=>{ 12 const deadline=performance.now()+30000; 13 while(performance.now()<deadline){ 14 const messages=events.slice(start),failure=messages.find(x=>x.type==='error');if(failure)throw Error(failure.message); 15 const found=messages.find(predicate);if(found)return found; 16 if(stepping){const step=messages.find(x=>x.type==='state'&&x.state==='step'&&x.id>consumedStep);if(step){consumedStep=step.id;worker.postMessage({type:'resume',id:step.id});}} 17 await sleep(); 18 } 19 throw Error('Resume probe timed out'); 20 }; 21 const reply=value=>worker.postMessage({type:'io-command',command:{run:value.run,kind:'reply',reply:value}}); 22 try{ 23 worker.postMessage({type:'compile',source:`!import ${lib('io')} as I 24 !import ${lib('io-text')} as T 25 !export main 26 main = I.io (I.require I.stdin (reader : I.require (T.readLine (T.lineCursor reader) 256) (reply : case reply of 27 [["line" value _] _] = I.pure value 28 _ = I.fail "Invalid reply")))`}); 29 await wait(x=>x.type==='compiled'); 30 const run={type:'run-io',name:'main',output:'string',step:true}; 31 worker.postMessage(run);const step=await wait(x=>x.type==='state'&&x.state==='step'); 32 worker.postMessage({type:'resume',id:step.id,value:'wrong-kind'}); 33 worker.postMessage({type:'resume',id:step.id+999});await sleep(); 34 assert(!events.some(x=>x.type==='error'||x.type==='done'||x.type==='io-event'),'Malformed/stale step consumed wait'); 35 const input=(await wait(x=>x.type==='io-event'&&x.event.kind==='read',0,true)).event; 36 const terminal=new Terminal(input.run);terminal.handle(input); 37 for(const value of [undefined,'bad\nline','\ud800','a'.repeat(257)])assert(!terminal.line(input.run,input.id,value),'Invalid UI line admitted'); 38 for(const invalid of [ 39 {run:input.run,id:input.id,kind:'write',count:1}, 40 {run:input.run,id:input.id,kind:'read',bytes:[256],eof:false}, 41 {run:input.run,id:input.id,kind:'read',bytes:[],eof:false}, 42 {run:input.run,id:input.id+999,kind:'read',bytes:[65],eof:false}, 43 ])reply(invalid); 44 worker.postMessage({type:'resume',id:step.id,value:'stale'});await sleep(); 45 assert(!events.some(x=>x.type==='done'||x.type==='error'),'Invalid reply consumed wait'); 46 const accepted=terminal.line(input.run,input.id,'λ雪🙂');reply(accepted); 47 assert((await wait(x=>x.type==='done',0,true)).result.value==='λ雪🙂','Fresh reply mismatch'); 48 const mark=events.length;worker.postMessage({...run,step:false}); 49 const next=(await wait(x=>x.type==='io-event'&&x.event.kind==='read',mark)).event; 50 assert(next.run!==input.run,'Run identity reused');reply(accepted);await sleep(); 51 assert(!events.slice(mark).some(x=>x.type==='done'||x.type==='error'),'Previous run reply accepted'); 52 const fresh=new Terminal(next.run);fresh.handle(next);reply(fresh.line(next.run,next.id,'fresh')); 53 assert((await wait(x=>x.type==='done',mark)).result.value==='fresh','Fresh run failed'); 54 }finally{worker.terminate();} 55 }