import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import test from 'node:test'; import ts from 'typescript'; const source = readFileSync(new URL('../src/lib/graphParents.ts', import.meta.url), 'utf8'); const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 } }).outputText; const { visibleParentResolver } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`); function oldResolver(hash, visible, commits, seen = new Set()) { if (visible.has(hash)) return [hash]; if (seen.has(hash)) return []; seen.add(hash); const commit = commits.get(hash); return commit ? [...new Set(commit.parents.flatMap(parent => oldResolver(parent, visible, commits, new Set(seen))))] : []; } test('preserves parent order and deduplicates converging paths', () => { const items = [{hash:'tip',parents:['a','b']},{hash:'a',parents:['x','y']},{hash:'b',parents:['y','z']}]; const resolve = visibleParentResolver(items,new Set(['x','y','z'])); assert.deepEqual(resolve('tip'),['x','y','z']); assert.deepEqual(resolve('missing'),[]); assert.deepEqual(resolve('x'),['x']); }); test('matches previous traversal across deterministic merge DAGs and visibility filters', () => { let seed=42; const random=()=>((seed=(Math.imul(seed,1664525)+1013904223)>>>0)/2**32); for(let run=0;run<80;run++) { const items=Array.from({length:40},(_,i)=>({hash:String(i),parents:i===39?[]:[String(i+1),...(random()<.5?[String(i+1+Math.floor(random()*(39-i)))]:[])]})); const visible=new Set(items.filter(()=>random()<.35).map(x=>x.hash)); const resolve=visibleParentResolver(items,visible); const map=new Map(items.map(x=>[x.hash,x])); for(const item of items) assert.deepEqual(resolve(item.hash),oldResolver(item.hash,visible,map)); } }); test('handles 20000 hidden ancestors without overflowing the call stack', () => { const items=Array.from({length:20000},(_,i)=>({hash:String(i),parents:[String(i+1)]})); assert.deepEqual(visibleParentResolver(items,new Set(['20000']))('0'),['20000']); }); test('shared merge ancestry is expanded only once', () => { let reads=0; const items=Array.from({length:30},(_,i)=>({hash:String(i),get parents(){reads++;return i===29?['root']:[String(i+1),String(Math.min(i+2,29))];}})); const resolve=visibleParentResolver(items,new Set(['root'])); assert.deepEqual(resolve('0'),['root']); const initial=reads; assert.deepEqual(resolve('1'),['root']); assert.equal(reads,initial); assert.ok(reads<300); }); const items=Array.from({length:24},(_,i)=>({hash:String(i),parents:i===23?['root']:[String(i+1),String(Math.min(i+2,23))]})); const visible=new Set(['root']);const map=new Map(items.map(x=>[x.hash,x])); const before=performance.now();oldResolver('0',visible,map);const oldMs=performance.now()-before; const after=performance.now();visibleParentResolver(items,visible)('0');const newMs=performance.now()-after; console.log(`Synthetic shared-ancestry benchmark (24 commits): old ${oldMs.toFixed(2)} ms, new ${newMs.toFixed(2)} ms`);