conformance.mjs (4913B)
1 import { readFileSync, writeFileSync, mkdtempSync, rmSync, existsSync, readdirSync } from 'node:fs'; 2 import { tmpdir } from 'node:os'; 3 import { join } from 'node:path'; 4 import { strict as assert } from 'node:assert'; 5 import { Library, TricuError } from '../src/lib.js'; 6 7 export function conformance(path, fixtureDir, specification, copier) { 8 const f = JSON.parse(readFileSync(specification, 'utf8')); 9 const library = new Library(path), ctx = library.context(), other = library.context(); 10 const report = {}; 11 const load = (file, name) => ctx.loadBundle(readFileSync(join(fixtureDir, file)), name); 12 try { 13 report.numbers = f.numbers.map(n => ctx.toNumber(ctx.number(BigInt(n))).toString()); 14 report.strings = f.strings.map(s => ctx.toString(ctx.string(s))); 15 report.bytesHex = f.bytesHex.map(h => ctx.toBytes(ctx.bytes(Buffer.from(h,'hex'))).toString('hex')); 16 const worker = load('id.arboricx','id'), argument = ctx.string(f.identity); 17 assert.equal(typeof worker, 'bigint'); 18 const application = ctx.apply(worker, argument); 19 ctx.release(worker); ctx.release(argument); 20 report.identity = ctx.toString(ctx.reduce(application)); 21 let append = load('append.arboricx','append'); 22 for (const text of f.append) append = ctx.apply(append,ctx.string(text)); 23 report.append = ctx.toString(append); 24 report.booleans = ['true','false'].map(name => ctx.toBool(load(name+'.arboricx',name))); 25 report.notQKind = ctx.kind(ctx.reduce(load('notQ.arboricx','not?'))); 26 report.outcomes = []; 27 for (const code of [0,2,9]) { 28 const program = ctx.fork(ctx.string('tricuIO'),ctx.fork(ctx.number(2),ctx.fork(ctx.number(code),ctx.string('opaque payload')))); 29 const {outcome,value,cleanup} = ctx.runIO(program); 30 report.outcomes.push({outcome,payload:ctx.toString(value),cleanupKind:ctx.kind(cleanup)}); 31 for (const handle of [program,value,cleanup]) ctx.release(handle); 32 } 33 const root = ctx.string('owned'), alias = ctx.retain(root); 34 assert.notEqual(root,alias); 35 ctx.release(root); 36 assert.equal(ctx.toString(alias),'owned'); 37 const wrong = ctx.stem(ctx.stem(ctx.leaf())); 38 report.negativeStatuses = [() => ctx.release(root), () => other.retain(alias), 39 () => ctx.toNumber(wrong), () => ctx.toBool(wrong), 40 () => ctx.loadBundle(Buffer.from('bad bundle'),'id'), () => load('id.arboricx','missing'), 41 () => ctx.runIO(ctx.leaf())].map(operation => { 42 try { operation(); } catch (error) { 43 assert.ok(error instanceof TricuError); assert.ok(error.message.length); return error.status; 44 } 45 throw new Error('invalid operation accepted'); 46 }); 47 assert.throws(() => ctx.string('\ud800'), /surrogate/); 48 for (const number of [-1n,2n**64n,9007199254740992,1.5]) assert.throws(() => ctx.number(number)); 49 if (copier) checkCopier(library,copier,f); 50 assert.deepEqual(report,f.expected); 51 } finally { other.close(); ctx.close(); } 52 assert.throws(() => ctx.leaf(), e => e instanceof TricuError && e.status === 1); 53 ctx.close(); // idempotent 54 for (let i=0;i<100;i++) { const fresh=library.context(); fresh.close(); } 55 return report; 56 } 57 58 function checkCopier(library,bundle,f) { 59 const dir=mkdtempSync(join(tmpdir(),'tricu-js-')),ctx=library.context(); 60 const source=join(dir,'in'),target=join(dir,'out'),body=Buffer.from(f.copyHex.repeat(f.copyRepeats),'hex'); 61 const bytes=readFileSync(bundle),worker=ctx.loadBundle(bytes,'copy'); 62 const run = (fn,destination=target) => ctx.runIO(ctx.apply(ctx.apply(fn,ctx.string(source)),ctx.string(destination))); 63 const fdCount = () => existsSync('/proc/self/fd') ? readdirSync('/proc/self/fd').length : null; 64 try { 65 writeFileSync(source,body); 66 let baseline; 67 for (let i=0;i<3;i++) { 68 const r=run(worker); 69 assert.equal(r.outcome,0); assert.equal(ctx.kind(r.cleanup),0); 70 assert.equal(ctx.toBool(ctx.child(r.value,0)),true); 71 assert.deepEqual(readFileSync(target),body); 72 if (i===0) baseline=fdCount(); else assert.equal(fdCount(),baseline); 73 ctx.release(r.value); ctx.release(r.cleanup); 74 } 75 const cancelled=run(ctx.loadBundle(bytes,'cancelledCopy')); 76 assert.equal(cancelled.outcome,0); 77 assert.equal(ctx.toString(ctx.child(cancelled.value,0)),'cancelled'); 78 assert.equal(ctx.toString(ctx.child(ctx.child(cancelled.value,1),0)),'requested'); 79 assert.deepEqual(readFileSync(target),body.subarray(0,4096)); 80 const failed=run(worker,join(dir,'missing-parent','out')); 81 assert.equal(failed.outcome,0); assert.equal(ctx.toBool(ctx.child(failed.value,0)),false); 82 assert.equal(fdCount(),baseline); 83 rmSync(source); rmSync(target); 84 const missing=run(worker); 85 assert.equal(missing.outcome,0); assert.equal(ctx.toBool(ctx.child(missing.value,0)),false); 86 assert.equal(existsSync(target),false); 87 } finally { ctx.close(); rmSync(dir,{recursive:true,force:true}); } 88 }