tricu

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

check.mjs (8456B)


      1 import assert from 'node:assert/strict';
      2 import {readFile} from 'node:fs/promises';
      3 import {WasmRuntime} from './runtime.mjs';
      4 const wasm = await readFile(process.argv[2]);
      5 function snapshotTree(snapshot) {
      6   assert.equal(snapshot.truncated,false);
      7   const nodes=new Map(snapshot.nodes.map(n=>[n.id,n])),pending=[snapshot.root],chars=[];
      8   while(pending.length) {
      9     assert.ok(chars.length<10000);
     10     const node=nodes.get(pending.pop());assert.ok(node);
     11     chars.push(String(node.children.length));pending.push(...[...node.children].reverse());
     12   }
     13   return chars.join('');
     14 }
     15 const vectors = JSON.parse(await readFile('ext/demo-site/test/conformance.json'));
     16 for (const c of vectors.cases) {
     17   const g = await WasmRuntime.create(wasm);
     18   try {
     19     const applied=g.apply(g.fromTernary(c.program),c.arguments.map(x=>g.fromTernary(x)));
     20     assert.equal(snapshotTree(g.preview(applied,256)),c.result);
     21     assert.equal(g.toTernary(applied),c.result);
     22   }
     23   finally { g.dispose(); }
     24 }
     25 const g = await WasmRuntime.create(wasm);
     26 const other = new WasmRuntime({exports: g.e});
     27 try {
     28   assert.throws(() => g.force(other.leaf), /status 1/);
     29   const dead = g.stem(g.leaf); g.release(dead);
     30   assert.throws(() => g.force(dead), /status 1/);
     31   assert.throws(() => g.load(new Uint8Array([1,2,3])), /status 5/);
     32   assert.throws(() => g.handle('arb_run_io', g.leaf, 0), /status 6/);
     33   const bytes = Uint8Array.from([0,127,128,255]);
     34   assert.deepEqual(g.toBytes(g.ofBytes(bytes)),bytes);
     35   const id = g.load(await readFile('test/fixtures/id.arboricx'));
     36   const arg = g.fromTernary('2100'), app = g.app(id,arg);
     37   assert.throws(() => g.force(app,0n), /status 3/);
     38   assert.equal(g.toTernary(app),'2100');
     39   g.release(id); g.release(arg); g.call('arb_collect');
     40   assert.equal(g.toTernary(app),'2100');
     41   const q = g.fromTernary('212110021100'), loop = g.app(q,q);
     42   assert.equal(g.toTernary(g.app(g.fromTernary('200'),loop)), '0');
     43   assert.throws(() => g.force(loop,100n), /status 3/);
     44   const before = g.e.memory.buffer;
     45   g.e.memory.grow(1);
     46   assert.notEqual(g.e.memory.buffer, before);
     47   assert.equal(g.toTernary(app),'2100');
     48 } finally { g.dispose(); other.dispose(); }
     49 assert.throws(() => g.force(0n), /Disposed/);
     50 console.log('WASM: 300 independent conformance vectors, handles, GC, malformed bundle, fuel, laziness, memory growth passed');
     51 
     52 const r = await WasmRuntime.create(wasm);
     53 try {
     54   const initial = r.stats().owned;
     55   const vectors = JSON.parse(await readFile('ext/demo-site/demos.json')).codecVectors;
     56   r.scope(() => {
     57     for (const v of vectors.filter(v=>v.kind === 'string')) {
     58       assert.equal(r.toTernary(r.ofText(v.value)),v.tree);
     59       assert.equal(r.toText(r.fromTernary(v.tree)),v.value);
     60     }
     61   });
     62   assert.equal(r.e.arb_host_alloc(512*1024*1024),0);
     63   assert.equal(r.toText(r.leaf),'');
     64   assert.throws(() => r.scope(async () => {}), /synchronous/);
     65   const unicode = '\ufeff\0a\r\néλ雪🙂\uffff\u{10ffff}';
     66   r.scope(() => {
     67     const tree = r.ofText(unicode);
     68     assert.equal(r.toText(tree), unicode);
     69     assert.throws(() => r.toText(tree,2), /status 5/);
     70     assert.throws(() => r.ofText('🙂',0), /limit/);
     71     assert.throws(() => r.ofText('\ud800'), /surrogate/);
     72     assert.throws(() => r.ofText('\udc00'), /surrogate/);
     73     assert.equal(r.toText(r.ofText('',0),0),'');
     74     const single = n => r.fork(r.handle('arb_of_number',n),r.leaf);
     75     for (const n of [0xd800n,0xdfffn,0x110000n]) assert.throws(() => r.toText(single(n)), /status 5/);
     76     const noncanonical = r.fork(r.leaf,r.leaf);
     77     assert.throws(() => r.toText(r.fork(noncanonical,r.leaf)), /status 5/);
     78     for (const bytes of [[0x80],[0xc0,0xaf],[0xed,0xa0,0x80],[0xf4,0x90,0x80,0x80]])
     79       assert.throws(() => r.buffer(Uint8Array.from(bytes),p=>r.handle('arb_host_of_text',p,bytes.length,10)), /status 5/);
     80   });
     81   assert.equal(r.stats().owned,initial);
     82   assert.throws(() => r.scope(() => { r.fromTernary('2100'); throw new Error('unwind'); }), /unwind/);
     83   assert.throws(() => r.scope(({keep}) => { keep(r.ofText('temporary')); throw new Error('unwind'); }), /unwind/);
     84   assert.throws(() => r.fromTernary('2100x'), /Invalid/);
     85   assert.equal(r.stats().owned,initial);
     86   const kept = r.scope(({keep}) => keep(r.scope(({keep}) => keep(r.ofText(unicode)))));
     87   r.collect(); assert.equal(r.toText(kept),unicode); r.release(kept);
     88   const id = r.load(await readFile('test/fixtures/id.arboricx'));
     89   r.call('arb_set_gc_policy',32,1);
     90   r.collect(); const baseline = r.stats();
     91   for (let i=0;i<200;i++) {
     92     r.scope(() => {
     93       const input = r.ofText(unicode), applied = r.app(id,input);
     94       r.release(input); // application owns its graph, not the input handle
     95       assert.equal(r.toText(applied),unicode);
     96       assert.throws(() => r.toTernary(applied,2), /expansion/);
     97     });
     98     r.collect();
     99     assert.equal(r.stats().owned,baseline.owned);
    100     assert.equal(r.stats().nodes,baseline.nodes);
    101     assert.ok(r.stats().slots < 100);
    102   }
    103   r.release(id);
    104 } finally { r.dispose(); }
    105 console.log('WASM: strict Unicode, nested scope escape/failure cleanup, and 200 retained-context GC cycles passed');
    106 
    107 const previewRuntime = await WasmRuntime.create(wasm);
    108 try {
    109   const p = previewRuntime;
    110   p.call('arb_set_gc_policy',1,1);
    111   p.scope(() => {
    112     let root = p.retain(p.leaf);
    113     for(let i=0;i<31;i++) {const next=p.fork(root,root);p.release(root);root=next;}
    114     const before=p.stats().owned;
    115     for(let i=0;i<100;i++) {
    116       const snapshot=p.preview(root,32);
    117       assert.equal(snapshot.nodes.length,32);assert.equal(snapshot.truncated,false);
    118       assert.equal(snapshot.root,0);
    119       for(let j=0;j<31;j++)assert.deepEqual(snapshot.nodes[j],{id:j,kind:'fork',children:[j+1,j+1]});
    120       assert.deepEqual(snapshot.nodes[31],{id:31,kind:'leaf',children:[]});
    121       assert.equal(p.stats().owned,before);
    122     }
    123     const deep=p.fromTernary('1'.repeat(1000)+'0');
    124     const bounded=p.preview(deep,256);
    125     assert.equal(bounded.nodes.length,256);assert.equal(bounded.truncated,true);
    126     p.release(deep);
    127     const short=p.preview(root,1);
    128     assert.equal(short.nodes.length,1);assert.equal(short.truncated,true);
    129     assert.deepEqual(short.nodes[0].children,[1,1]);
    130     assert.throws(()=>p.preview(root,0),/budget/);
    131     assert.throws(()=>p.preview(root,257),/budget/);
    132     const dead=p.retain(root);p.release(dead);
    133     assert.throws(()=>p.preview(dead),/status 1/);
    134     const q=p.fromTernary('212110021100'),loop=p.app(q,q);
    135     const lazy=p.stem(loop);
    136     assert.equal(p.preview(lazy,1,0n).truncated,true); // frontier isn't forced
    137     assert.throws(()=>p.preview(lazy,2,10n),/status 3/);
    138     assert.equal(p.stats().owned,before+3);
    139     // A failed demand leaves output fields untouched, and no snapshot buffer.
    140     p.view().setBigUint64(p.scratch,0x123456789abcdef0n,true);
    141     assert.equal(p.e.arb_host_preview(p.ctx,loop,2,10n,p.scratch,p.scratch+8),3);
    142     assert.equal(p.view().getBigUint64(p.scratch,true),0x123456789abcdef0n);
    143     assert.deepEqual(p.preview(p.leaf,1),{root:0,nodes:[{id:0,kind:'leaf',children:[]}],truncated:false});
    144     // Snapshot bytes own their labels; collection/reuse cannot mutate a result.
    145     const saved=structuredClone(short);p.release(root);p.collect();
    146     assert.deepEqual(short,saved);
    147   });
    148   assert.equal(p.stats().owned,1);
    149 } finally {previewRuntime.dispose();}
    150 console.log('WASM: bounded bulk preview sharing, exact limits, lazy frontier, fuel failure, GC and ownership passed');
    151 
    152 const unbounded=await WasmRuntime.create(wasm,{unbounded:true});
    153 try {
    154   const r=unbounded;
    155   await r.scopeAsync(async()=>{
    156     const h=r.retain(r.leaf);
    157     await assert.rejects(r.scopeAsync(async()=>{}),/exclusive/);
    158     r.scope(({keep})=>keep(r.retain(h)));
    159     await Promise.resolve();r.collect();assert.equal(r.toTernary(h),'0');
    160   });
    161   assert.equal(r.stats().owned,1);
    162   await assert.rejects(r.scopeAsync(async()=>{r.retain(r.leaf);throw Error('host failure');}),/host failure/);
    163   assert.equal(r.stats().owned,1);
    164   r.scope(()=>{
    165     const delayed=r.app(r.leaf,r.leaf);
    166     assert.throws(()=>r.force(delayed,0n),/status 3/);
    167     assert.equal(r.toTernary(delayed),'10');
    168     assert.equal(r.preview(delayed).nodes.length,2);
    169   });
    170   assert.equal(r.stats().owned,1);
    171   assert.throws(()=>r.check(2),/status 2/);assert.equal(r.poisoned,true);
    172 }finally{unbounded.dispose();}
    173 console.log('WASM: explicit unbounded demands, finite negative control, exclusive async cleanup and fatal allocation classification passed');