modelguard-observatory / test_engine.js
Hugdrigo's picture
Upload 28 files
85920d3 verified
Raw History Blame Contribute Delete
5.25 kB
const assert=require('node:assert/strict'), fs=require('node:fs'),E=require('./engine.js');const data=JSON.parse(fs.readFileSync(__dirname+'/evaluation_snapshots.json'));const rows=E.parseRows(data),manifest=JSON.parse(fs.readFileSync(__dirname+'/benchmark_manifest.json'));let tested=0;function check(title,fn){try{fn();tested++;console.log('PASS',title);}catch(e){console.error('FAIL',title,e);process.exitCode=1;}}
check('reproducible model/evaluation rows',()=>assert.equal(rows.length,736));
check('baseline split lengths',()=>{for(const m of E.METHODS){assert.equal(E.select(rows,m,'iid').length,130);assert.equal(E.select(rows,m,'challenge').length,54);}});
check('manifest provenance',()=>{assert.equal(manifest.trainCount,842);assert.equal(manifest.sourceType,'synthetic_curated');});
check('validated two independent splits',()=>assert.equal(E.audit(rows).clean,true));
check('duplicate detection',()=>assert.throws(()=>E.parseRows([rows[0],rows[0]]),/Duplicate/));
check('input labels rejected',()=>assert.throws(()=>E.parseRows([{...rows[0],truth:'superiority'}]),/Invalid model/));
check('input empty rejected',()=>assert.throws(()=>E.parseRows([]),/Expected an array/));
check('structured object loading',()=>assert.equal(E.parseRows({rows:rows.slice(0,2)}).length,2));
check('model challenge NB 40/54',()=>{let m=E.metrics(E.select(rows,'nb','challenge'));assert.equal(m.correct,40);assert.equal(m.n,54);});
check('hybrid 42/54 challenge',()=>{const m=E.metrics(E.select(rows,'hybrid','challenge'));assert.equal(m.correct,42);});
check('NB IID 130/130',()=>assert.equal(E.metrics(E.select(rows,'nb','iid')).correct,130));
check('char IID 124/130',()=>assert.equal(E.metrics(E.select(rows,'char','iid')).correct,124));
check('empty metrics safe',()=>assert.equal(E.metrics([]).accuracy,null));
check('macro F1 bounded',()=>{for(const m of E.METHODS){const x=E.metrics(E.select(rows,m,'challenge'));assert(x.macroF1>=0&&x.macroF1<=1)}});
check('slice sample size exact',()=>{const sl=E.segments(E.select(rows,'hybrid','challenge'),'language');assert.equal(sl.length,3);assert.equal(sl.reduce((s,x)=>s+x.n,0),54);});
check('truth-class slice returns six',()=>assert.equal(E.segments(E.select(rows,'hybrid','iid'),'truth').length,6));
check('drift equal splits returns zeros',()=>{const a=E.select(rows,'hybrid','iid'),d=E.drift(a,a);assert.equal(d.languageJSD,0);assert.equal(d.lengthPSI,0);});
check('drift different splits has change',()=>{const d=E.compare(rows,'hybrid').drift;assert(d.lengthPSI>.25);assert(d.languageJSD<.01);});
check('JSD commutative',()=>assert(Math.abs(E.jsd([1,8,2],[6,2,3])-E.jsd([6,2,3],[1,8,2]))<1e-9));
check('JSD bounded by one',()=>assert(Math.abs(E.jsd([20,0],[0,20])-1)<1e-8));
check('PSI zero if equal',()=>assert.equal(E.psi([1,3,4],[1,3,4]),0));
check('bootstrap deterministic',()=>{const a=E.select(rows,'hybrid','challenge');assert.deepEqual(E.ciAccuracy(a,80,10),E.ciAccuracy(a,80,10));});
check('bootstrap confidence interval ordered',()=>{const x=E.ciAccuracy(E.select(rows,'hybrid','challenge'));assert(x.low<=x.high&&x.high<=1);});
check('paired comparison same method zero',()=>{const z=E.pairedComparison(rows,'hybrid','hybrid');assert.equal(z.delta,0);assert.equal(z.n,54);assert.equal(z.bWins,0);});
check('paired comparison signs reverse',()=>{const a=E.pairedComparison(rows,'nb','hybrid'),b=E.pairedComparison(rows,'hybrid','nb');assert(Math.abs(a.delta+b.delta)<1e-8);assert.equal(a.aWins,b.bWins);});
check('gate default fails on length shift',()=>assert.equal(E.gate(rows,'hybrid').pass,false));
check('gate threshold changes not metrics',()=>{const a=E.gate(rows,'hybrid'),b=E.gate(rows,'hybrid',{maxLengthPSI:1});assert.equal(a.pass,false);assert.equal(b.pass,true);assert.equal(a.checks.length,6);});
check('CSV roundtrip quoted comma and newline',()=>{const a=E.csv([{id:'a',note:'hello, "world"\n2'}],['id','note']);assert.deepEqual(E.parseCsv(a),[{id:'a',note:'hello, "world"\n2'}]);});
check('CSV formula injection prevented',()=>{const x=E.csv([{a:'=HYPERLINK("https://x")'}],['a']);assert(x.includes("'=HYPERLINK"));});
check('CSV malformed quotes rejected',()=>assert.throws(()=>E.parseCsv('id,txt\n1,"hi'),/Unclosed/));
check('eval records with exotic text remain safe structure',()=>{const x=E.parseRows([{...rows[0],text:'<img src=x onerror=alert(1)>',id:'X'}]);assert(x[0].text.includes('<img'));});
check('audit detecting duplicated split IDs',()=>{const x=rows.slice(0,2).map((r,i)=>({...r,id:'x',split:i?'challenge':'iid'}));assert.equal(E.audit(x).splitIdOverlap,1);});
check('empty sample intervals forbidden',()=>assert.throws(()=>E.ciAccuracy([]),/Empty/));
check('invalid bootstrap sample count forbidden',()=>assert.throws(()=>E.ciAccuracy(rows,3),/iterations/));
check('length-bin histogram PSI finite',()=>{const x=E.compare(rows,'word').drift.lengthPSI;assert(Number.isFinite(x));});
check('label recall values finite',()=>{const z=E.metrics(E.select(rows,'nb','challenge'));assert(z.perLabel.every(x=>Number.isFinite(x.recall)));});
check('reproducible report score',()=>assert(Math.abs(E.compare(rows,'hybrid').challenge.accuracy-.7777777777777778)<1e-12));
console.log('TESTS',tested,process.exitCode?'FAIL':'PASS');if(process.exitCode)process.exit(1);