Files
cell/tests/wota.ce

221 lines
10 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* wota_test.cm  selfcontained testsuite 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, nonzero 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 → lowercase 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()