tricu

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

gc.test.mjs (5126B)


      1 import test from 'node:test';
      2 import assert from 'node:assert/strict';
      3 import {readFile} from 'node:fs/promises';
      4 import {Graph} from '../dist/graph.js';
      5 import {text,natural,readText} from '../dist/codecs.js';
      6 import {runIO} from '../dist/io.js';
      7 test('tracing preserves sharing and stable handles; stale IDs are never reused',()=>{
      8  const g=new Graph({fuel:10000}),garbage=g.stem(g.leaf);let root=g.leaf;
      9  for(let i=0;i<31;i++)root=g.fork(root,root);
     10  const before=g.preview(root);g.collect([root]);
     11  assert.equal(g.stats.nodes,32);assert.equal(g.stats.reclaimed,1);
     12  assert.deepEqual(g.preview(root),before);
     13  const fresh=g.stem(g.leaf);assert.notEqual(fresh,garbage);
     14  assert.throws(()=>g.observe(garbage),/Invalid node handle/);
     15  assert.equal(g.observe(root).node.left,g.observe(root).node.right);
     16 });
     17 test('marking traces application/ref edges without forcing discarded divergence',()=>{
     18  const g=new Graph({fuel:10000}),q=g.fromTernary('212110021100'),loop=g.app(q,q);
     19  const value=g.stem(loop);g.collect([value]);assert.equal(g.observe(value).node.child,loop);
     20  const result=g.app(g.fromTernary('200'),value);
     21  assert.equal(g.force(result),g.leaf);g.collect([result]);
     22  assert.equal(g.stats.nodes,2);assert.equal(g.force(result),g.leaf);
     23 });
     24 test('automatic reducer collections preserve every pre-entry caller handle',()=>{
     25  const g=new Graph({nodes:1000,fuel:100000},{threshold:8});
     26  const caller=g.fromTernary('2100'),I=g.fromTernary('21100');
     27  let app=g.leaf;for(let i=0;i<60;i++)app=g.app(I,app);
     28  assert.equal(g.force(app),g.leaf);assert.equal(g.toTernary(caller),'2100');
     29  assert.ok(g.stats.collections>0);g.collect([app,caller]);
     30  assert.ok(g.stats.reclaimed>50);assert.equal(g.force(app),g.leaf);
     31 });
     32 test('GC-enabled evaluator matches all 300 Haskell cases including all frame variants',async()=>{
     33  const corpus=JSON.parse(await readFile(new URL('./conformance.json',import.meta.url)));
     34  for(const c of corpus.cases){
     35   const g=new Graph({nodes:1000,fuel:100000},{threshold:8});
     36   const fn=g.fromTernary(c.program),args=c.arguments.map(x=>g.fromTernary(x));
     37   const result=g.apply(fn,args);assert.equal(g.toTernary(result),c.result);
     38   g.collect([result]);assert.equal(g.toTernary(result),c.result);
     39  }
     40 });
     41 test('collection is budgeted and commits atomically; no high-survival trace storm',()=>{
     42  const g=new Graph({fuel:5}),value=g.stem(g.leaf);
     43  assert.throws(()=>g.collect([value]),/fuel/);assert.equal(g.stats.nodes,2);assert.equal(g.stats.collections,0);
     44  const high=new Graph({nodes:40,fuel:2000},{threshold:2});let root=high.leaf;
     45  for(let i=0;i<20;i++)root=high.stem(root);
     46  assert.equal(high.force(root),root);assert.ok(high.stats.collections<=2);
     47 });
     48 test('owned IO collection retains saved continuations and input across pauses',async()=>{
     49  const g=new Graph({fuel:100000},{threshold:32});
     50  const action=(tag,payload=g.leaf)=>g.fork(natural(g,BigInt(tag)),payload);
     51  const pure=g.fork(g.leaf,natural(g,0n)); // K Leaf; the continuation ignores line data.
     52  const bind=action(1,g.fork(action(11),g.fork(g.leaf,action(0,text(g,'done')))));
     53  const io=g.fork(text(g,'tricuIO'),g.fork(natural(g,1n),bind));
     54  let resolve;const read=new Promise(r=>{resolve=r;});let waiting=false;
     55  const resultPromise=runIO(g,io,{before:async()=>{},after(){},write(){},running(){},read(){waiting=true;return read;}},true);
     56  while(!waiting)await Promise.resolve();
     57  assert.ok(g.stats.collections>=2);resolve('λ雪🙂');
     58  const result=await resultPromise;assert.equal(readText(g,result),'done');
     59  // A previously unrelated caller handle is invalidated only by the explicit
     60  // owned-IO contract, never by automatic reducer collections.
     61  assert.throws(()=>g.observe(pure),/Invalid node handle/);
     62 });
     63 test('explicit Infinity profile removes graph/fuel/stack ceilings without changing defaults',()=>{
     64  const bounded=new Graph({nodes:2,fuel:100,stack:1});bounded.stem(bounded.leaf);
     65  assert.throws(()=>bounded.stem(bounded.leaf),/allocation/);
     66  const g=new Graph({nodes:Infinity,fuel:Infinity,stack:Infinity},{threshold:8});
     67  const I=g.fromTernary('21100');let root=g.leaf;
     68  for(let i=0;i<100;i++)root=g.app(I,root);
     69  assert.equal(g.force(root),g.leaf);assert.equal(g.stats.fuel,Infinity);
     70  assert.ok(g.stats.collections>0);assert.ok(g.stats.work>100);
     71  for(const fuel of [NaN,-Infinity,0,-1,1.5])assert.throws(()=>new Graph({fuel}),/budget/);
     72 });
     73 test('owned IO extra roots preserve compiled definitions for repeated input runs',async()=>{
     74  const g=new Graph({fuel:100000},{threshold:32});
     75  const definition=g.fork(text(g,'tricuIO'),g.fork(natural(g,1n),g.fork(natural(g,11n),g.leaf)));
     76  const identity=g.fromTernary('21100');const roots=[definition,identity];
     77  let reads=0;
     78  for(const input of ['first','λ雪🙂','third']){
     79   const result=await runIO(g,definition,{before:async()=>{},after(){},write(){},running(){},read:async()=>{reads++;return input;}},true,roots.values());
     80   assert.equal(readText(g,result),input);g.collect(roots);
     81   assert.equal(g.toTernary(identity),'21100');
     82  }
     83  assert.equal(reads,3);assert.ok(g.stats.collections>=6);
     84  assert.ok(g.stats.fuel<100000); // Work stays cumulative across replays.
     85 });