tricu

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

zig-session-probe.mjs (7087B)


      1 // Independent complete Haskell captures, exercised through Zig's coarse adapter.
      2 const assert=(ok,message)=>{if(!ok)throw Error(message);};
      3 const equal=(a,b,label)=>assert(JSON.stringify(a)===JSON.stringify(b),label+': '+JSON.stringify(a).slice(0,160));
      4 async function rejects(fn,pattern){try{await fn();}catch(e){assert(pattern.test(e.message),'Wrong rejection: '+e.message);return;}throw Error('Expected '+pattern);}
      5 export async function run() {
      6  const base=new URL('../',document.querySelector('script[type=module][src]').src);
      7  const {create}=await import(new URL('runtime/zig/site-session.mjs',base));
      8  const fixtures=await(await fetch('/zig-fixtures.json')).json();
      9  const catalogue=await(await fetch(new URL('catalogue.json',base))).json();
     10  const report={packages:0,applications:0,compiledDemos:0,checks:[],memory:[]};
     11  const host=input=>({before:async()=>{},after:()=>{},read:async()=>input,write:()=>{},running:()=>{}});
     12  const bytes=async hash=>new Uint8Array(await(await fetch('/_arboricx/bundle/hash/'+hash)).arrayBuffer());
     13  const s=await create({base,compiler:false});
     14  try {
     15   for(const [name,demo] of Object.entries(fixtures.demos)) {
     16    await s.load(await bytes(demo.hash),demo.hash);report.packages++;
     17    for(const c of demo.haskellCases){
     18     let terminal='',before;
     19     const hooks=host(c.input);
     20     hooks.before=async e=>{assert(!before,'Missing after');before=e;};
     21     hooks.after=e=>{assert(e.step===before?.step&&e.name===before.name,'Trace ordering/name');before=undefined;};
     22     hooks.read=async()=>{s.collect();return c.input;};hooks.write=text=>{terminal+=text;};
     23     equal(await s.inspect(c.export,c.arguments.map(value=>({type:'tree',value})),demo.mode,hooks),c.result,'Full golden '+name);
     24     equal(terminal,c.terminal,'Terminal '+name);report.applications++;
     25    }
     26   }
     27   const negatives=await(await fetch('/zig-negative.json')).json();
     28   for(const [name,pattern] of [['version',/version/],['unsupported',/Unsupported/],['abort',/aborted/],['output',/output limit/],['actions',/action limit/]]){
     29    const f=negatives[name];await s.load(new Uint8Array(f.bytes),f.hash);
     30    await rejects(()=>s.run('root',[],'tree','io',host()),pattern);
     31   }
     32   const shared=negatives.sharing;await s.load(new Uint8Array(shared.bytes),shared.hash);
     33   const preview=(await s.run('root',[],'tree','pure',host())).value;
     34   assert(!preview.truncated&&preview.nodes.length===41,'Physical sharing expanded');
     35   assert(preview.nodes.filter(n=>n.kind==='fork').every(n=>n.children[0]===n.children[1]),'Lost child aliases');
     36   const intro=fixtures.demos.intro;await s.load(await bytes(intro.hash),intro.hash);
     37   const invoke=(arg,type)=>s.run('identity',[arg],type,'pure',host());
     38   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');}
     39   for(const value of [true,false])equal((await invoke({type:'bool',value},'bool')).value,value,'Boolean');
     40   equal((await invoke({type:'bytes',value:[0,1,127,128,255]},'bytes')).value,[0,1,127,128,255],'Bytes');
     41   await rejects(()=>invoke({type:'number',value:'256'},'bool'),/Boolean/);
     42   await rejects(()=>invoke({type:'list',value:[{type:'number',value:'256'}]},'bytes'),/byte/);
     43   await rejects(()=>invoke({type:'list',value:[{type:'number',value:'55296'}]},'string'),/status 5/);
     44   await rejects(()=>invoke({type:'string',value:'\ud800'},'string'),/surrogate/);
     45   const record={type:'list',value:[{type:'list',value:[{type:'string',value:'__proto__'},{type:'number',value:'0'},{type:'string',value:'safe'}]}]};
     46   const decoded=(await invoke(record,'record')).value;assert(Object.hasOwn(decoded,'__proto__')&&decoded.__proto__==='safe','Prototype-safe record');
     47   await rejects(()=>invoke({type:'list',value:[record.value[0],record.value[0]]},'record'),/unique/);
     48   equal((await invoke({type:'list',value:[{type:'number',value:'42'}]},'list')).value.length,1,'List');
     49   const original=await bytes(intro.hash);
     50   await rejects(()=>s.load(original,'0'.repeat(64)),/SHA-256 mismatch/);
     51   const mutable=original.slice(),loading=s.load(mutable,intro.hash);mutable.fill(0);await loading;
     52   equal((await invoke({type:'number',value:'42'},'number')).value,'42','Load must authenticate an immutable snapshot');
     53   const baseline=s.diagnostics().owned;
     54   for(let i=0;i<50;i++){
     55    equal((await invoke({type:'string',value:'λ雪🙂'},'string')).value,'λ雪🙂','Retained run');
     56    s.collect();equal(s.diagnostics().owned,baseline,'Temporary owned-handle leak');
     57    report.memory.push(s.diagnostics().linearMemoryBytes);
     58   }
     59   assert(Math.max(...report.memory.slice(25))<=Math.max(...report.memory.slice(0,25))+65536,'Memory failed to plateau');
     60   report.checks.push('117 complete published goldens; IO GC/trace ordering/limits; seven codecs/negative controls; physical sharing; ownership and memory plateau');
     61   const greet=fixtures.demos.greet;await s.load(await bytes(greet.hash),greet.hash);
     62   let entered;const paused=new Promise(resolve=>{entered=resolve;});
     63   const running=s.run(greet.haskellCases[0].export,[],'string','io',{...host(''),before:()=>{entered();return new Promise(()=>{});}});
     64   running.catch(()=>{});await paused;s.dispose();await rejects(()=>running,/Disposed/);assert(s.disposed,'Disposed session reused');
     65  }finally{s.dispose();}
     66  const c=await create({base,compiler:true});
     67  try {
     68   for(const [name,demo] of Object.entries(fixtures.demos)){
     69    const entry=catalogue.find(x=>x.hash===demo.hash);assert(entry,'Catalogue fixture pin');
     70    await c.compile(demo.source,entry.environment);
     71    const first=demo.haskellCases[0];
     72    equal(await c.inspect(first.export,first.arguments.map(value=>({type:'tree',value})),demo.mode,host(first.input)),first.result,'Compiled '+name);report.compiledDemos++;
     73   }
     74   const intro=fixtures.demos.intro;await c.compile(intro.source,'empty');
     75   await rejects(()=>c.compile('name = unavailableName','empty'),/Compiler diagnostic/);
     76   // Retired stale-definition retention: a failed replacement cannot expose the
     77   // previous program or its imported scope. Runtime/IO errors still retain it.
     78   await rejects(()=>c.run('answer',[],'number','pure',host()),/Missing definition/);
     79   const greet=fixtures.demos.greet;await c.compile(greet.source,'empty');
     80   await rejects(()=>c.run(greet.haskellCases[0].export,[],'string','io',{...host('x'),before:async()=>{throw Error('host failed');}}),/host failed/);
     81   const first=greet.haskellCases[0];equal(await c.inspect(first.export,[],greet.mode,host(first.input)),first.result,'Host-error cleanup preserves definitions');
     82   await c.compile('q x = x x\nlazy = t (q q) t\naborted = t \"tricuIO\" (t 1 (t 2 (q q)))','empty');
     83   const partial=await c.preview('lazy',1);assert(partial.nodes.length===1&&partial.truncated,'Preview forced excluded divergent child');
     84   await rejects(()=>c.run('aborted',[],'tree','io',host()),/aborted/);
     85   report.checks.push('ten complete native source demos; compile-error invalidation/host-error retention; nonforcing divergent preview/abort');
     86  }finally{c.dispose();}
     87  return report;
     88 }