tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

zig-session-probe.mjs (9201B)


      1 import {terminalHost} from './terminal-host.mjs';
      2 // Independent complete Haskell captures, exercised through Zig's coarse adapter.
      3 const assert=(ok,message)=>{if(!ok)throw Error(message);};
      4 const equal=(a,b,label)=>assert(JSON.stringify(a)===JSON.stringify(b),label+': '+JSON.stringify(a).slice(0,160));
      5 async function rejects(fn,pattern){try{await fn();}catch(e){assert(pattern.test(e.message),'Wrong rejection: '+e.message);return;}throw Error('Expected '+pattern);}
      6 export async function run(control) {
      7  const phase=async name=>{if(control)await fetch(control+'/phase',{method:'POST',body:JSON.stringify({name})});};
      8  const base=new URL('../',document.querySelector('script[type=module][src]').src);
      9  const {Terminal}=(await import(new URL('dist/probe-api.js',base))).ioV2Terminal;
     10  const {create}=await import(new URL('runtime/zig/site-session.mjs',base));
     11  const fixtures=await(await fetch('/zig-fixtures.json')).json();
     12  const catalogue=await(await fetch(new URL('catalogue.json',base))).json();
     13  const releases=(await(await fetch(new URL('packages.json',base))).json()).releases;
     14  const report={packages:0,applications:0,compiledDemos:0,checks:[],memory:[]};
     15  const host=input=>({before:async()=>{},after:()=>{},read:async()=>input,write:()=>{},running:()=>{}});
     16  const bytes=async hash=>new Uint8Array(await(await fetch('/_arboricx/bundle/hash/'+hash)).arrayBuffer());
     17  const s=await create({base,compiler:false});
     18  try {
     19   for(const [name,demo] of Object.entries(fixtures.demos)) {
     20    await phase("full golden "+name);
     21    await s.load(await bytes(demo.hash),demo.hash);report.packages++;
     22    for(const c of demo.haskellCases){
     23     let terminal='',before;
     24     const hooks=demo.mode==='io'?terminalHost(Terminal,c.input):host(c.input);
     25     hooks.before=async e=>{assert(!before,'Missing after');before=e;};
     26     hooks.after=e=>{assert(e.step===before?.step&&e.name===before.name,'Trace ordering/name');before=undefined;};
     27     if(demo.mode==='io'){const event=hooks.v2.event;hooks.v2.event=e=>{s.collect();event(e);};}
     28     else {hooks.read=async()=>{s.collect();return c.input;};hooks.write=text=>{terminal+=text;};}
     29     equal(await s.inspect(c.export,c.arguments.map(value=>({type:'tree',value})),demo.mode,hooks),c.result,'Full golden '+name);
     30     equal(demo.mode==='io'?hooks.terminalText:terminal,c.terminal,'Terminal '+name);report.applications++;
     31    }
     32   }
     33   const negatives=await(await fetch('/zig-negative.json')).json();
     34   {
     35    const f=negatives['v1-output'];await s.load(new Uint8Array(f.bytes),f.hash);
     36    let calls=0;const forbidden=()=>{calls++;throw Error('retired callback');};
     37    await rejects(()=>s.run('root',[],'tree','io',{...host(),before:forbidden,write:forbidden,read:forbidden}),/Unsupported tricuIO version/);
     38    equal(calls,0,'Retired v1 output never invokes host');
     39   }
     40   for(const [name,pattern] of [['version',/version/],['version-effect',/version/],['unsupported',/Unsupported/],['abort',/Unsupported tricuIO version/],['output',/Unsupported tricuIO version/],['actions',/Unsupported tricuIO version/]]){
     41    const f=negatives[name];await s.load(new Uint8Array(f.bytes),f.hash);
     42    await rejects(()=>s.run('root',[],'tree','io',host()),pattern);
     43   }
     44   {
     45    const f=negatives['version-effect'];await s.load(new Uint8Array(f.bytes),f.hash);
     46    let callbacks=0;const forbidden=()=>{callbacks++;throw Error('version gate reached host callback');};
     47    await rejects(()=>s.run('root',[],'tree','io',{before:forbidden,after:forbidden,read:forbidden,write:forbidden,running:forbidden}),/version/);
     48    equal(callbacks,0,'Unsupported version invoked host');
     49    report.checks.push('IO v2 rejected before host callbacks; no v1 retry');
     50   }
     51   const shared=negatives.sharing;await s.load(new Uint8Array(shared.bytes),shared.hash);
     52   const preview=(await s.run('root',[],'tree','pure',host())).value;
     53   assert(!preview.truncated&&preview.nodes.length===41,'Physical sharing expanded');
     54   assert(preview.nodes.filter(n=>n.kind==='fork').every(n=>n.children[0]===n.children[1]),'Lost child aliases');
     55   const intro=fixtures.demos.intro;await s.load(await bytes(intro.hash),intro.hash);
     56   const invoke=(arg,type)=>s.run('identity',[arg],type,'pure',host());
     57   for(const vector of fixtures.codecVectors){const type=vector.kind,value=type==='number'?String(vector.value):vector.value;equal((await invoke({type,value},type)).value,value,'Scalar codec');}
     58   for(const value of [true,false])equal((await invoke({type:'bool',value},'bool')).value,value,'Boolean');
     59   equal((await invoke({type:'bytes',value:[0,1,127,128,255]},'bytes')).value,[0,1,127,128,255],'Bytes');
     60   await rejects(()=>invoke({type:'number',value:'256'},'bool'),/Boolean/);
     61   await rejects(()=>invoke({type:'list',value:[{type:'number',value:'256'}]},'bytes'),/byte/);
     62   await rejects(()=>invoke({type:'list',value:[{type:'number',value:'55296'}]},'string'),/status 5/);
     63   await rejects(()=>invoke({type:'string',value:'\ud800'},'string'),/surrogate/);
     64   const scalars={type:'list',value:[{type:'string',value:'λ🙂'},{type:'number',value:'1267650600228229401496703205376'}]};
     65   equal((await invoke(scalars,'values')).value,scalars.value,'Decoded list preserves text and exact big naturals');
     66   await rejects(()=>invoke({type:'list',value:[{type:'bool',value:true}]},'values'),/number/);
     67   equal((await invoke({type:'bytes',value:Array(4096).fill(42)},'values')).value.length,4096,'Decoded lists are not capped at 32 preview items');
     68   equal((await invoke({type:'list',value:[{type:'number',value:'42'}]},'list')).value.length,1,'List');
     69   const original=await bytes(intro.hash);
     70   await rejects(()=>s.load(original,'0'.repeat(64)),/SHA-256 mismatch/);
     71   const mutable=original.slice(),loading=s.load(mutable,intro.hash);mutable.fill(0);await loading;
     72   equal((await invoke({type:'number',value:'42'},'number')).value,'42','Load must authenticate an immutable snapshot');
     73   const baseline=s.diagnostics().owned;
     74   for(let i=0;i<50;i++){
     75    equal((await invoke({type:'string',value:'λ雪🙂'},'string')).value,'λ雪🙂','Retained run');
     76    s.collect();equal(s.diagnostics().owned,baseline,'Temporary owned-handle leak');
     77    report.memory.push(s.diagnostics().linearMemoryBytes);
     78   }
     79   assert(Math.max(...report.memory.slice(25))<=Math.max(...report.memory.slice(0,25))+65536,'Memory failed to plateau');
     80   report.checks.push(`${report.applications} complete published goldens; v2 IO GC and retired framing rejection; scalar/list/tree codecs and negative controls; physical sharing; ownership and memory plateau`);
     81   const greet=fixtures.demos.greet;await s.load(await bytes(greet.hash),greet.hash);
     82   let entered;const paused=new Promise(resolve=>{entered=resolve;});
     83   const pausedHost=terminalHost(Terminal,'');pausedHost.v2.stepping=true;pausedHost.before=()=>{entered();return new Promise(()=>{});};
     84   const running=s.run(greet.haskellCases[0].export,[],'string','io',pausedHost);
     85   running.catch(()=>{});await paused;s.dispose();await rejects(()=>running,/Disposed/);assert(s.disposed,'Disposed session reused');
     86  }finally{s.dispose();}
     87  const c=await create({base,compiler:true});
     88  try {
     89   for(const [name,demo] of Object.entries(fixtures.demos)){
     90    const entry=catalogue.find(x=>x.hash===demo.hash);
     91    assert(entry||name==='intro'&&releases.some(r=>r.name==='intro'&&r.version==='0.2.0'&&r.hash===demo.hash),'Current catalogue pin or explicitly retained historical intro publication');
     92    await phase("source compile "+name);
     93    await c.compile(demo.source,entry?.environment??'empty');
     94    const first=demo.haskellCases[0];
     95    equal(await c.inspect(first.export,first.arguments.map(value=>({type:'tree',value})),demo.mode,demo.mode==='io'?terminalHost(Terminal,first.input):host(first.input)),first.result,'Compiled '+name);report.compiledDemos++;
     96   }
     97   const intro=fixtures.demos.intro;await c.compile(intro.source,'empty');
     98   await rejects(()=>c.compile('name = unavailableName','empty'),/Compiler diagnostic/);
     99   // Retired stale-definition retention: a failed replacement cannot expose the
    100   // previous program or its imported scope. Runtime/IO errors still retain it.
    101   await rejects(()=>c.run('answer',[],'number','pure',host()),/Missing definition/);
    102   const greet=fixtures.demos.greet;await c.compile(greet.source,'empty');
    103   const failingHost=terminalHost(Terminal,'x');failingHost.v2.stepping=true;failingHost.before=async()=>{throw Error('host failed');};
    104   await rejects(()=>c.run(greet.haskellCases[0].export,[],'string','io',failingHost),/host failed/);
    105   const first=greet.haskellCases[0];equal(await c.inspect(first.export,[],greet.mode,terminalHost(Terminal,first.input)),first.result,'Host-error cleanup preserves definitions');
    106   await c.compile('q x = x x\nlazy = t (q q) t\naborted = t \"tricuIO\" (t 2 (t 2 (q q)))','empty');
    107   const partial=await c.preview('lazy',1);assert(partial.nodes.length===1&&partial.truncated,'Preview forced excluded divergent child');
    108   await rejects(()=>c.run('aborted',[],'tree','io',terminalHost(Terminal,null)),/IO v2 failed/);
    109   report.checks.push('current published browser source demos and retained historical fixtures; compile-error invalidation/host-error retention; nonforcing divergent preview/abort');
    110  }finally{c.dispose();}
    111  return report;
    112 }