/* * wota_test.cm – self‑contained test‑suite for the Wota encode/decode module * *** rewritten to run in an environment that ONLY supports * Blobs; TypedArrays / ArrayBuffers / DataView are GONE. *** * * Exit status 0 → all tests passed, non‑zero otherwise. */ 'use strict' var wota = use('wota') var os = use('os') var Blob = use('blob') /*──────────────────────────────────────────────────────────────────────────*/ /* Helper utilities */ /*──────────────────────────────────────────────────────────────────────────*/ def EPSILON = 1e-12 function stone_if_needed(b) { if (!stone.p(b)) stone(b) } /* Convert an array of octets to a stone Blob */ function bytes_to_blob(bytes) { var b = new Blob() for (var i = 0; i < bytes.length; i++) { var byte = bytes[i] for (var bit = 7; bit >= 0; bit--) b.write_bit((byte >> bit) & 1) } stone(b) return b } /* Parse hex → Blob */ function hex_to_blob(hex) { if (hex.length % 2) hex = '0' + hex // odd nibble safety var bytes = [] for (var i = 0; i < hex.length; i += 2) bytes.push(parseInt(hex.substr(i, 2), 16)) return bytes_to_blob(bytes) } /* Blob → lower‑case hex */ function blob_to_hex(blob) { stone_if_needed(blob) var bytes = [] for (var i = 0; i < blob.length; i += 8) { var byte = 0 for (var bit = 0; bit < 8; bit++) byte = (byte << 1) | (blob.read_logical(i + bit) ? 1 : 0) bytes.push(byte) } return bytes.map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() } function is_blob(x) { return x && typeof x == 'object' && typeof x.length == 'number' && typeof x.read_logical == 'function' } /* Deep comparison capable of Blobs + tolerance for floating diff */ function deep_compare(expected, actual, path = '') { if (expected == actual) return { passed: true, messages: [] } if (typeof expected == 'number' && typeof actual == 'number') { if (isNaN(expected) && isNaN(actual)) return { passed: true, messages: [] } var diff = Math.abs(expected - actual) if (diff <= EPSILON) return { passed: true, messages: [] } return { passed: false, messages: [`Value mismatch at ${path}: ${expected} vs ${actual} (diff ${diff})`] } } if (is_blob(expected) && is_blob(actual)) { stone_if_needed(expected); stone_if_needed(actual) if (expected.length != actual.length) return { passed: false, messages: [`Blob length mismatch at ${path}: ${expected.length} vs ${actual.length}`] } for (var i = 0; i < expected.length; i++) { if (expected.read_logical(i) != actual.read_logical(i)) return { passed: false, messages: [`Blob bit mismatch at ${path}[${i}]`] } } return { passed: true, messages: [] } } if (Array.isArray(expected) && Array.isArray(actual)) { if (expected.length != actual.length) return { passed: false, messages: [`Array length mismatch at ${path}: ${expected.length} vs ${actual.length}`] } var msgs = [] for (var i = 0; i < expected.length; i++) { var res = deep_compare(expected[i], actual[i], `${path}[${i}]`) if (!res.passed) msgs.push(...res.messages) } return { passed: msgs.length == 0, messages: msgs } } if (typeof expected == 'object' && expected && typeof actual == 'object' && actual) { var expKeys = Object.keys(expected).sort() var actKeys = Object.keys(actual).sort() if (JSON.stringify(expKeys) != JSON.stringify(actKeys)) return { passed: false, messages: [`Object keys mismatch at ${path}: ${expKeys} vs ${actKeys}`] } var msgs = [] for (var k of expKeys) { var res = deep_compare(expected[k], actual[k], `${path}.${k}`) if (!res.passed) msgs.push(...res.messages) } return { passed: msgs.length == 0, messages: msgs } } return { passed: false, messages: [`Value mismatch at ${path}: ${JSON.stringify(expected)} vs ${JSON.stringify(actual)}`] } } /*──────────────────────────────────────────────────────────────────────────*/ /* Test matrix */ /*──────────────────────────────────────────────────────────────────────────*/ var testarr = [] var hex = 'a374' for (var i = 0; i < 500; i++) { testarr.push(1); hex += '61' } function bb() { return bytes_to_blob.apply(null, arguments) } // shorthand var testCases = [ { input: 0, expectedHex: '60' }, { input: 2023, expectedHex: 'e08f67' }, { input: -1, expectedHex: '69' }, { input: 7, expectedHex: '67' }, { input: -7, expectedHex: '6f' }, { input: 1023, expectedHex: 'e07f' }, { input: -1023, expectedHex: 'ef7f' }, { input: Math.pow(2, 55) - 1, expectedHex: 'e0ffffffffffffff' }, { input: -Math.pow(2, 55), expectedHex: 'e000000000000000' }, { input: undefined, expectedHex: '70' }, { input: false, expectedHex: '72' }, { input: true, expectedHex: '73' }, { input: -1.01, expectedHex: '5a65' }, { input: 98.6, expectedHex: '51875a' }, { input: -0.5772156649, expectedHex: 'd80a95c0b0bd69' }, { input: -1.00000000000001, expectedHex: 'd80e96deb183e98001' }, { input: -10000000000000, expectedHex: 'c80d01' }, { input: Math.pow(2, 55), expectedHex: 'd80e01' }, { input: '', expectedHex: '10' }, { input: 'cat', expectedHex: '13636174' }, { input: 'U+1F4A9 πÇîπüåπéôπüíτ╡╡µûçσ¡ùπÇì ┬½≡ƒÆ⌐┬╗', expectedHex: '9014552b314634413920e00ce046e113e06181fa7581cb0781b657e00d20812b87e929813b' }, { input: bytes_to_blob([0xff, 0xaa]), expectedHex: '8010ffaa' }, { input: bytes_to_blob([0xf0, 0xe3, 0x20, 0x80]), expectedHex: '8019f0e32080' }, { input: testarr, expectedHex: hex }, { input: [], expectedHex: '20' }, { input: [1, 2, 3], expectedHex: '23616263' }, { input: [-1, 0, 1.5], expectedHex: '2369605043' }, { input: {}, expectedHex: '30' }, { input: { a: 1, b: 2 }, expectedHex: '32116161116262' }, { input: { num: 42, arr: [1, -1, 2.5], str: 'test', obj: { x: true } }, expectedHex: '34216e756d622a2173747214746573742161727223616965235840216f626a21117873' }, { input: new Blob(), expectedHex: '00' }, { input: [[]], expectedHex: '2120' }, { input: { '': '' }, expectedHex: '311010' }, { input: 1e-10, expectedHex: 'd00a01' }, { input: { a: 1, b: 2 }, replacer: (k, v) => typeof v == 'number' ? v * 2 : v, expected: { a: 2, b: 4 }, testType: 'replacer' }, { input: { x: 'test', y: 5 }, replacer: (k, v) => k == 'x' ? v + '!' : v, expected: { x: 'test!', y: 5 }, testType: 'replacer' }, { input: { a: 1, b: 2 }, reviver: (k, v) => typeof v == 'number' ? v * 3 : v, expected: { a: 3, b: 6 }, testType: 'reviver' }, { input: { x: 'test', y: 10 }, reviver: (k, v) => k == 'y' ? v + 1 : v, expected: { x: 'test', y: 11 }, testType: 'reviver' } ] /*──────────────────────────────────────────────────────────────────────────*/ /* Execution */ /*──────────────────────────────────────────────────────────────────────────*/ var results = [] var testCount = 0 for (var t of testCases) { testCount++ var name = `Test ${testCount}: ${JSON.stringify(t.input)}${t.testType ? ' (' + t.testType + ')' : ''}` var passed = true var msgs = [] try { var enc = t.replacer ? wota.encode(t.input, t.replacer) : wota.encode(t.input) if (!is_blob(enc)) { passed = false; msgs.push('encode() should return a Blob') } else { if (t.expectedHex) { var gotHex = blob_to_hex(enc) if (gotHex != t.expectedHex.toLowerCase()) msgs.push(`Hex encoding differs (info): exp ${t.expectedHex}, got ${gotHex}`) } var dec = t.reviver ? wota.decode(enc, t.reviver) : wota.decode(enc) var exp = t.expected != undefined ? t.expected : t.input var cmp = deep_compare(exp, dec) if (!cmp.passed) { passed = false; msgs.push(...cmp.messages) } } } catch (e) { passed = false; msgs.push('Exception: ' + e) } results.push({ name, passed, msgs }) if (!passed) { log.console('\nFailure detail for ' + name + '\n' + msgs.join('\n') + '\n') } } /*──────────────────────────────────────────────────────────────────────────*/ /* Summary */ /*──────────────────────────────────────────────────────────────────────────*/ log.console('\nTest Summary:') var passCount = 0 for (var r of results) { log.console(`${r.name} – ${r.passed ? 'Passed' : 'Failed'}`) if (r.msgs.length && r.passed) log.console(' ' + r.msgs.join('\n ')) if (r.passed) passCount++ } log.console(`\nResult: ${passCount}/${testCount} tests passed`) if (passCount == testCount) { log.console('Overall: PASSED'); os.exit(0) } log.console('Overall: FAILED') $_.stop()