// Recompute every expected file of vector-conformance under this engine's // WebAssembly, and require each to equal the committed file byte for byte; and the // authoring form beside each payload. The default stroke set's SHA-256 is required to // be the one Annex B of the extension states. // // node verify.mjs import { createHash } from 'node:crypto'; import { readFileSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; const [wasmPath, root] = process.argv.slice(2); if (!wasmPath || !root) { console.error('usage: node verify.mjs '); process.exit(2); } const { instance } = await WebAssembly.instantiate(readFileSync(wasmPath), {}); const x = instance.exports; const enc = new TextEncoder(); function put(bytes) { const p = x.wv_alloc(bytes.length); new Uint8Array(x.memory.buffer, p, bytes.length).set(bytes); return p; } function out() { return new Uint8Array(x.memory.buffer, x.wv_out_ptr(), x.wv_out_len()).slice(); } for (const f of readdirSync(join(root, 'sets')).filter((n) => n.endsWith('.wos1')).sort()) { const b = readFileSync(join(root, 'sets', f)); const p = put(b); x.wv_add_set(p, b.length); x.wv_free(p, b.length); } let vectors = 0, forms = 0, bad = 0; // Annex B: the default stroke set's digest. { const doc = readFileSync(join(root, '..', 'extensions', 'graphics-vector.md'), 'utf8'); const annex = doc.slice(doc.indexOf('## Annex B.')); const line = annex.split('\n').find((l) => l.includes('the set (`WOS1`)')) || ''; const stated = (line.match(/[0-9a-f]{64}/) || [''])[0]; const have = createHash('sha256').update(readFileSync(join(root, 'sets', 'stroke-ascii.wos1'))).digest('hex'); if (stated.length !== 64 || have !== stated) { bad += 1; console.error(`ANNEX B: sets/stroke-ascii.wos1 hashes to ${have}, Annex B states ${stated || 'nothing'}`); } else { console.log(`the default stroke set is Annex B's: ${have}`); } } for (const group of readdirSync(join(root, 'vectors')).sort()) { for (const f of readdirSync(join(root, 'vectors', group)).sort()) { const dot = f.lastIndexOf('.'); const name = f.slice(0, dot), ext = f.slice(dot + 1); if (name === 'index' || (ext === 'json' && !['arith', 'flatten'].includes(group))) continue; const data = readFileSync(join(root, 'vectors', group, f)); const [g, e] = [enc.encode(group), enc.encode(ext)]; const [pg, pe, pd] = [put(g), put(e), put(data)]; const status = x.wv_recompute(pg, g.length, pe, e.length, pd, data.length); const got = out(); x.wv_free(pg, g.length); x.wv_free(pe, e.length); vectors += 1; const want = readFileSync(join(root, 'expected', group, name + '.json')); if (status !== 0) { bad += 1; console.error(`FAIL ${group}/${f}: ${new TextDecoder().decode(got)}`); } else if (Buffer.compare(Buffer.from(got), want) !== 0) { bad += 1; console.error(`DIFF ${group}/${f}: the recomputed file differs`); } if (ext === 'wvg1') { x.wv_form(pd, data.length); forms += 1; if (Buffer.compare(Buffer.from(out()), readFileSync(join(root, 'vectors', group, name + '.json'))) !== 0) { bad += 1; console.error(`FORM ${group}/${f}: the authoring form differs`); } } x.wv_free(pd, data.length); } } console.log(`recomputed ${vectors} vectors and ${forms} authoring forms, under ${process.version}`); if (bad > 0) { console.error(`${bad} files failed`); process.exit(1); } console.log('vector-conformance: every vector verifies');