tricu

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

bundle.test.mjs (8250B)


      1 import test from 'node:test';
      2 import assert from 'node:assert/strict';
      3 import {readFile} from 'node:fs/promises';
      4 import {createHash} from 'node:crypto';
      5 import {Graph} from '../dist/graph.js';
      6 import {COMPILER_BUNDLE_LIMIT,decodeBundle,loadBundle,bundleHash,fetchBytes,fetchBundle,BUNDLE_LIMIT} from '../dist/bundle.js';
      7 import {makeBundle,sizedBundle} from './bundle-fixture.mjs';
      8 // Independently counted from the authenticated compiler manifest with the
      9 // Python bundle reader; older indexed compiler releases remain retained.
     10 const COMPILER_EXPORT_COUNT=1191;
     11 const data=JSON.parse(await readFile(new URL('../demos.json',import.meta.url)));
     12 test('stable packaged sources/pins and real compiler bundle decode',async()=>{
     13   for(const [name,demo] of Object.entries(data.demos)){
     14     const bytes=await readFile(new URL(`../bundles/${demo.hash}.arboricx`,import.meta.url));
     15     assert.equal(createHash('sha256').update(bytes).digest('hex'),demo.hash);
     16     assert.equal(createHash('sha256').update(demo.source).digest('hex'),demo.sourceSha256);
     17     assert.equal(await readFile(new URL(`../demos/${name}.tri`,import.meta.url),'utf8'),demo.source);
     18     assert.ok(decodeBundle(bytes).exports.length>0);
     19     if(demo.supportSourceSha256)assert.equal(createHash('sha256').update(await readFile(new URL('../demos/practical-support.tri',import.meta.url))).digest('hex'),demo.supportSourceSha256);
     20   }
     21   const descriptor=JSON.parse(await readFile(new URL('../compiler.json',import.meta.url)));
     22   const bytes=await readFile(new URL('../bundles/'+descriptor.hash+'.arboricx',import.meta.url));
     23   assert.equal(createHash('sha256').update(bytes).digest('hex'),data.compilerBundleSha256);
     24   assert.equal(decodeBundle(bytes).exports.length,COMPILER_EXPORT_COUNT);
     25   const bundle=decodeBundle(bytes,COMPILER_BUNDLE_LIMIT);assert.equal(bundle.exports.length,COMPILER_EXPORT_COUNT);
     26   const g=new Graph();loadBundle(g,bundle);assert.equal(g.stats.nodes,bundle.nodes.length);
     27 });
     28 test('direct indexed loading preserves exponential sharing and root aliases',()=>{
     29   const records=[[]];for(let i=1;i<32;i++)records.push([i-1,i-1]);
     30   const b=decodeBundle(makeBundle(records,[['huge',31],['alias',31]]));
     31   const g=new Graph(), roots=loadBundle(g,b);
     32   assert.equal(g.stats.nodes,32);assert.equal(roots.get('huge'),roots.get('alias'));
     33   const node=g.observe(roots.get('huge')).node;assert.equal(node.left,node.right);
     34   assert.equal(g.preview(roots.get('huge')).nodes.length,32);
     35 });
     36 test('malformed header, versions, section bounds/duplicates/reserved, trailing data',()=>{
     37   const good=makeBundle([[],[0]]);
     38   for(const [offset,value,width] of [[0,0,1],[8,2,2],[10,1,2],[16,1,1],[24,0,8],[32,2,4],[36,2,2],[40,1,2],[42,1,2],[44,0,8],[52,0xffffffff,8],[60,1,4]]){
     39     const bad=Buffer.from(good);
     40     if(width===1)bad[offset]=value;else if(width===2)bad.writeUInt16BE(value,offset);else if(width===4)bad.writeUInt32BE(value,offset);else bad.writeBigUInt64BE(BigInt(value),offset);
     41     assert.throws(()=>decodeBundle(bad),undefined,`offset ${offset}`);
     42   }
     43   const critical=Buffer.from(good);critical.writeUInt32BE(100,32);assert.throws(()=>decodeBundle(critical),/critical/);
     44   for(const n of [0,5,31,95,good.length-1])assert.throws(()=>decodeBundle(good.subarray(0,n)));
     45   assert.throws(()=>decodeBundle(Buffer.concat([good,Buffer.from([0])])),/Trailing/);
     46   assert.throws(()=>decodeBundle(new Uint8Array(BUNDLE_LIMIT+1)),/limit/);
     47 });
     48 test('manifest semantics, export ambiguity/ABI, DAG acyclicity/duplicate control',()=>{
     49   assert.throws(()=>decodeBundle(makeBundle([[],[0]],undefined,{evaluation:'eager'})),/semantics/);
     50   assert.throws(()=>decodeBundle(makeBundle([[],[0]],undefined,{abi:'other'})),/ABI/);
     51   assert.throws(()=>decodeBundle(makeBundle([[],[0]],[['',1]])),/export/);
     52   assert.throws(()=>decodeBundle(makeBundle([[],[0]],[['x',1],['x',1]])),/export/);
     53   assert.throws(()=>decodeBundle(makeBundle([[],[0]],[['x',9]])),/bounds/);
     54   assert.throws(()=>decodeBundle(makeBundle([[],[1]])),/backward/);
     55   assert.throws(()=>decodeBundle(makeBundle([[],[]])),/Duplicate/);
     56   assert.throws(()=>decodeBundle(makeBundle([[],[0],[0]])),/Duplicate/);
     57 });
     58 test('bounded streamed download, corrupt pin, HTTP errors, canonical hash spelling',async()=>{
     59   const good=makeBundle([[],[0]]),hash=bundleHash(createHash('sha256').update(good).digest('hex'));
     60   for(const value of ['', 'A'.repeat(64),'../secret', 'f'.repeat(65), 1])assert.throws(()=>bundleHash(value));
     61   const saved=globalThis.fetch;
     62   try{
     63     globalThis.fetch=async()=>new Response(good);assert.equal((await fetchBundle(new URL('https://example.test/a'),hash)).nodes.length,2);
     64     await assert.rejects(fetchBundle(new URL('https://example.test/a'),bundleHash('0'.repeat(64))),/mismatch/);
     65     globalThis.fetch=async()=>new Response('no',{status:404});await assert.rejects(fetchBytes(new URL('https://example.test/a')),/404/);
     66     globalThis.fetch=async()=>new Response(good,{headers:{'content-length':String(BUNDLE_LIMIT+1)}});await assert.rejects(fetchBytes(new URL('https://example.test/a')),/limit/);
     67     let cancelled=false;
     68     globalThis.fetch=async()=>new Response(new ReadableStream({start(controller){controller.enqueue(new Uint8Array(8));controller.enqueue(new Uint8Array(8));},cancel(){cancelled=true;}}));
     69     await assert.rejects(fetchBytes(new URL('https://example.test/a'),12),/limit/);assert.equal(cancelled,true);
     70   }finally{globalThis.fetch=saved;}
     71 });
     72 
     73 test('modern demo demand controls discard divergent fields under the package budget',async()=>{
     74   const data=JSON.parse(await readFile(new URL('../demos.json',import.meta.url)));
     75   let count=0;
     76   for(const demo of Object.values(data.demos))for(const c of demo.demandCases){
     77     const g=new Graph({nodes:250_000,fuel:20_000_000,stack:50_000},{threshold:100_000});
     78     const result=g.app(g.fromTernary(c.program,200_000),g.leaf);
     79     assert.equal(g.toTernary(result,50000),c.result,c.label);count++;
     80   }
     81   assert.equal(count,5);
     82 });
     83 
     84 test('compiler download profile retains header/stream cancellation and hash verification',async()=>{
     85   const {fetchBytes,fetchBundle,sha256,BUNDLE_LIMIT}=await import('../dist/bundle.js');
     86   const saved=globalThis.fetch;let cancelled=false;
     87   const url=new URL('../compiler.json',import.meta.url); // Mocked fetch only.
     88   try{
     89     globalThis.fetch=async()=>new Response(new ReadableStream({cancel(){cancelled=true;}}),
     90       {headers:{'content-length':String(COMPILER_BUNDLE_LIMIT+1)}});
     91     await assert.rejects(fetchBytes(url,COMPILER_BUNDLE_LIMIT),/download limit/);
     92     assert.equal(cancelled,true);
     93     for(const limit of [BUNDLE_LIMIT,COMPILER_BUNDLE_LIMIT]){
     94       cancelled=false;
     95       globalThis.fetch=async()=>new Response(new ReadableStream({
     96         start(controller){controller.enqueue(new Uint8Array(limit+1));},cancel(){cancelled=true;}
     97       }));
     98       await assert.rejects(fetchBytes(url,limit),/download limit/);assert.equal(cancelled,true);
     99     }
    100     const descriptor=JSON.parse(await readFile(new URL('../compiler.json',import.meta.url)));
    101     const bytes=await readFile(new URL('../bundles/'+descriptor.hash+'.arboricx',import.meta.url));
    102     globalThis.fetch=async()=>new Response(bytes);
    103     assert.equal((await fetchBundle(url,descriptor.hash)).exports.length,COMPILER_EXPORT_COUNT);
    104     assert.equal((await fetchBundle(url,descriptor.hash,COMPILER_BUNDLE_LIMIT)).exports.length,COMPILER_EXPORT_COUNT);
    105     await assert.rejects(fetchBundle(url,await sha256(new Uint8Array()),COMPILER_BUNDLE_LIMIT),/SHA-256/);
    106   }finally{globalThis.fetch=saved;}
    107 });
    108 
    109 
    110 test('ordinary packages accept exactly 16 MiB while draft compiler input stays at 2 MiB',async()=>{
    111  assert.equal(BUNDLE_LIMIT,16_777_216);assert.equal(COMPILER_BUNDLE_LIMIT,2_097_152);
    112  const bytes=sizedBundle(BUNDLE_LIMIT),hash=bundleHash(createHash('sha256').update(bytes).digest('hex'));
    113  assert.equal(decodeBundle(bytes).nodes.length,1);
    114  assert.throws(()=>decodeBundle(bytes,COMPILER_BUNDLE_LIMIT),/byte limit/);
    115  assert.throws(()=>decodeBundle(sizedBundle(BUNDLE_LIMIT+1)),/byte limit/);
    116  const saved=globalThis.fetch;
    117  try{
    118   globalThis.fetch=async()=>new Response(bytes);
    119   assert.equal((await fetchBundle(new URL('../compiler.json',import.meta.url),hash)).nodes.length,1);
    120  }finally{globalThis.fetch=saved;}
    121 });