tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

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 }