tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

bundle.test.mjs (8329B)


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