tricu

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

packages.test.mjs (10730B)


      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 {packageIndex,packageReleases,environmentProfile} from '../dist/packages.js';
      6 import {decodeBundle,loadBundle} from '../dist/bundle.js';
      7 import {Graph} from '../dist/graph.js';
      8 import {BROWSER_BUDGET} from '../dist/execution.js';
      9 const read=name=>readFile(new URL('../'+name,import.meta.url));
     10 
     11 test('named versions and demo environments are validated and pinned',async()=>{
     12  const releases=packageIndex(JSON.parse(await read('packages.json')));
     13  const catalogue=JSON.parse(await read('catalogue.json'));
     14  const environments=JSON.parse(await read('environments.json'));
     15  const compiler=JSON.parse(await read('compiler.json'));
     16  assert.equal(environments.compiler,compiler.hash);
     17  for(const entry of catalogue){
     18   const release=releases.find(r=>r.demo===entry.id&&r.hash===entry.hash);assert.ok(release);
     19   if(entry.environment===undefined){assert.equal(entry.preparation,'native');continue;}
     20   const {profile}=environmentProfile(environments,entry.environment);
     21   if(profile){
     22    const bytes=await read('bundles/'+profile.hash+'.arboricx');
     23    assert.equal(createHash('sha256').update(bytes).digest('hex'),profile.hash);
     24    const values=loadBundle(new Graph(),decodeBundle(bytes));
     25    for(const name of profile.names)assert.ok(values.has(name),name);
     26   }
     27  }
     28 });
     29 test('index and environment boundaries reject malformed paths, pins, duplicates and profiles',()=>{
     30  const r={name:'demo',version:'0.1',title:'A demo',hash:'a'.repeat(64),manifest:'b'.repeat(64)};
     31  assert.equal(packageIndex({schema:1,releases:[r]})[0].name,'demo');
     32  for(const change of [{name:'../x'},{version:'x/y'},{hash:'A'.repeat(64)},{manifest:'x'},{title:[]},{demo:'x/y'}])assert.throws(()=>packageIndex({schema:1,releases:[{...r,...change}]}));
     33  assert.throws(()=>packageIndex({schema:1,releases:[r,r]}),/Duplicate/);
     34  assert.throws(()=>packageIndex({schema:2,releases:[r]}));
     35  const descriptor={schema:1,compiler:'a'.repeat(64),profiles:{empty:null}};
     36  assert.equal(environmentProfile(descriptor,'empty').profile,null);
     37  assert.throws(()=>environmentProfile(descriptor,'toString'),/Unknown/);
     38  for(const names of [['bad name'],['x','x'],Array(129).fill('x'),['../x']])assert.throws(()=>environmentProfile({...descriptor,profiles:{test:{hash:r.hash,names}}},'test'));
     39 });
     40 test('browser execution has no application ceilings and retains GC',()=>{
     41  assert.deepEqual(BROWSER_BUDGET,{nodes:Infinity,fuel:Infinity,stack:Infinity});
     42  const open=new Graph(BROWSER_BUDGET,{threshold:2});let root=open.leaf;
     43  for(let i=0;i<100;i++){root=open.stem(root);open.collect([root]);}
     44  assert.ok(open.stats.nodes>10);assert.ok(open.stats.collections>0);
     45 });
     46 
     47 test('published native libraries have authenticated complete interfaces and no fabricated demo source',async()=>{
     48  const releases=packageIndex(JSON.parse(await read('packages.json')));
     49  const expected={
     50   'tricu-base':['append','matchResult','showNumber'],
     51   'tricu-file-io':['io','bind','readFile','sha256Hex','putStr','abortIO'],
     52   'arboricx-inspection':['inspectBundle','renderSummary','describeError'],
     53  };
     54  for(const [name,names] of Object.entries(expected))for(const version of ['0.1.0','0.1.1']){
     55   const release=releases.find(r=>r.name===name&&r.version===version);assert.ok(release);assert.equal(release.demo,undefined);
     56   const bytes=await read('bundles/'+release.hash+'.arboricx');
     57   assert.equal(createHash('sha256').update(bytes).digest('hex'),release.hash);
     58   assert.deepEqual([...loadBundle(new Graph(),decodeBundle(bytes)).keys()].sort(),names.sort());
     59  }
     60 });
     61 
     62 test('showcase packages retain browser-bounded complete library roots and native-only server identity',async()=>{
     63  const releases=packageIndex(JSON.parse(await read('packages.json')));
     64  for(const [name,required] of Object.entries({
     65   'arboricx-server':['arboricxServer','arboricxRouter','bundleHashValid?'],
     66   'tricu-http':['parseRequestLine','response','httpHandlerIO'],
     67   'tricu-intensional-contracts':['fullTree?','powerOfTwo?','sortedList?'],
     68   'tricu-graphs':['graphSort','graphCycle'],
     69   'tricu-parser':['parserBind','parserCommit','parserMatchReply','parserEof'],
     70   'tricu-formatter':['formatTri'],
     71  })){
     72   const release=releases.find(r=>r.name===name);assert.ok(release);assert.equal(release.demo,undefined);
     73   const bytes=await read('bundles/'+release.hash+'.arboricx');
     74   assert.equal(createHash('sha256').update(bytes).digest('hex'),release.hash);
     75   assert.ok(bytes.length<=16777216);
     76   const bundle=decodeBundle(bytes);assert.ok(bundle.nodes.length<=100000);
     77   const exports=loadBundle(new Graph(),bundle);
     78   for(const name of required)assert.ok(exports.has(name),name);
     79   if(release.name==='arboricx-server')assert.match(release.title,/native sockets and filesystem/);
     80  }
     81 });
     82 
     83 test('native parser demo source and goldens are authenticated with an editable native module environment',async()=>{
     84  const catalogue=JSON.parse(await read('catalogue.json')),demo=catalogue.find(e=>e.id==='parser');
     85  const goldens=JSON.parse(await read('parser-demo.json'));
     86  assert.equal(demo.preparation,'native');assert.equal(demo.environment,'empty');
     87  assert.equal(createHash('sha256').update(demo.source).digest('hex'),demo.sourceSha256);
     88  assert.equal(demo.source,await read('demos/parser.tri').then(b=>b.toString()));
     89  assert.equal(goldens.hash,demo.hash);assert.equal(goldens.sourceSha256,demo.sourceSha256);
     90  assert.ok(goldens.haskellCases.some(c=>c.record.status==='error'&&c.record.committed));
     91  assert.ok(goldens.haskellCases.some(c=>c.export==='parsePrefix'&&c.record.status==='ok'&&c.record.remaining));
     92 });
     93 
     94 test('both indexed compiler packages retain their exact independently authenticated artifacts',async()=>{
     95  const releases=packageIndex(JSON.parse(await read('packages.json'))),descriptor=JSON.parse(await read('compiler.json'));
     96  for(const [name,path,entrypoint] of [
     97   ['tricu-compiler','test/fixtures/compiler-library.arboricx','source_unit.compileStableModuleDefinitions'],
     98   ['tricu-source-builder','bootstrap/build-stable.arboricx','sourceBuildGraph'],
     99  ]){
    100   const reference=await readFile(new URL('../../../'+path,import.meta.url));
    101   const referenceHash=createHash('sha256').update(reference).digest('hex');
    102   const release=releases.find(r=>r.name===name&&r.hash===referenceHash);assert.ok(release);assert.equal(release.demo,undefined);
    103   const bytes=await read('bundles/'+release.hash+'.arboricx');
    104   assert.ok(bytes.equals(reference),'indexed current artifact matches authenticated reference');
    105   assert.equal(createHash('sha256').update(bytes).digest('hex'),release.hash);
    106   const {COMPILER_BUNDLE_LIMIT}=await import('../dist/bundle.js');
    107   const bundle=decodeBundle(bytes,COMPILER_BUNDLE_LIMIT);assert.ok(bundle.nodes.length<=100000);
    108   assert.ok(bundle.exports.some(e=>e.name===entrypoint));
    109   if(name==='tricu-compiler')assert.equal(release.hash,descriptor.hash);
    110   else{assert.equal(bundle.exports.length,1);assert.match(release.title,/native application compiler/);}
    111  }
    112 });
    113 
    114 test('new practical libraries expose complete interfaces and lexer reference source is authenticated',async()=>{
    115  const releases=packageIndex(JSON.parse(await read('packages.json')));
    116  for(const [name,required] of Object.entries({
    117   'tricu-math':['add','divMod','inRange','decimal','printable'],
    118   'tricu-lexer':['lexerScanWhile','lexerAdvance','lexerDispatch','lexerToken'],
    119   'tricu-binary':['readU8','readBytes','binaryUtf8?','errInvalidBinaryInput'],
    120   'tricu-list-traversal':['listContinue','listStop','listFoldUntil','listFoldTailUntil'],
    121   'tricu-patterns':['match','otherwise','cond','guard'],
    122  })){
    123   const release=releases.find(r=>r.name===name);assert.ok(release);assert.equal(release.demo,undefined);
    124   const bytes=await read('bundles/'+release.hash+'.arboricx');
    125   assert.equal(createHash('sha256').update(bytes).digest('hex'),release.hash);
    126   const names=decodeBundle(bytes).exports.map(e=>e.name);
    127   for(const name of required)assert.ok(names.includes(name),name);
    128   if(name==='tricu-list-traversal'||name==='tricu-patterns')assert.deepEqual(names.sort(),required.sort());
    129  }
    130  const demo=JSON.parse(await read('catalogue.json')).find(e=>e.id==='lexer'),goldens=JSON.parse(await read('lexer-demo.json'));
    131  assert.equal(demo.preparation,'native');assert.equal(demo.environment,'empty');
    132  assert.equal(demo.source,await read('demos/lexer.tri').then(b=>b.toString()));
    133  assert.equal(createHash('sha256').update(demo.source).digest('hex'),demo.sourceSha256);
    134  assert.equal(goldens.hash,demo.hash);assert.equal(goldens.sourceSha256,demo.sourceSha256);
    135  assert.ok(goldens.haskellCases.some(c=>c.record.line===2&&c.record.offset===3));
    136  assert.ok(goldens.haskellCases.some(c=>c.record.remaining==='é'));
    137 });
    138 
    139 test('release choices are newest-first, numeric, independent of publication order and non-mutating',()=>{
    140  const versions=['0.1.0','0.10.0','0.2.0','1.0.0-rc.2','1.0.0-rc.10','1.0.0','1.0.0-alpha','1.0.0-1'];
    141  const releases=versions.map(version=>({name:'demo',version,hash:'a'.repeat(64),manifest:'b'.repeat(64),title:version}));
    142  const expected=['1.0.0','1.0.0-rc.10','1.0.0-rc.2','1.0.0-alpha','1.0.0-1','0.10.0','0.2.0','0.1.0'];
    143  for(const input of [releases,[...releases].reverse()]){
    144   const snapshot=[...input];
    145   assert.deepEqual(packageReleases([...input,{...input[0],name:'other',version:'99.0.0'}],'demo').map(r=>r.version),expected);
    146   assert.deepEqual(input,snapshot);
    147  }
    148  assert.deepEqual(packageReleases(releases,'missing'),[]);
    149  const labels=['build2','build10','0.1.0'].map(version=>({...releases[0],version}));
    150  assert.deepEqual(packageReleases(labels,'demo').map(r=>r.version),['0.1.0','build10','build2']);
    151 });
    152 
    153 test('every catalogue demo is editable, source-authenticated and explicitly imports indexed manifests',async()=>{
    154  const catalogue=JSON.parse(await read('catalogue.json')),index=packageIndex(JSON.parse(await read('packages.json')));
    155  assert.equal(catalogue.length,16);
    156  assert.equal(catalogue[0].id,'names-values');
    157  assert.ok(!catalogue.some(e=>e.id==='intro'));
    158  for(const demo of catalogue){
    159   assert.equal(demo.environment,'empty',demo.id);
    160   assert.equal(demo.source,(await read('demos/'+demo.id+'.tri')).toString());
    161   assert.equal(createHash('sha256').update(demo.source).digest('hex'),demo.sourceSha256);
    162   assert.match(demo.source,/^!export /m);
    163   for(const line of demo.source.split('\n').filter(s=>s.startsWith('!import '))){
    164    const match=line.match(/^!import ([0-9a-f]{64}) (?:\*|as [A-Za-z_][A-Za-z0-9_]*)$/);assert.ok(match,line);
    165    assert.ok(index.some(r=>r.manifest===match[1]),line);
    166   }
    167   assert.doesNotMatch(demo.source,/Tree graph|copy it into Compiler|source is read-only|input box uses JSON/);
    168  }
    169 });