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');