var nota = use('nota'); var os = use('os'); var blob = use('blob') var EPSILON = 1e-12 function stone_if_needed(b) { if (!stone.p(b)) stone(b) } function bytes_to_blob(bytes) { var b = blob() for (var i = 0; i < length(bytes); i++) { var byte = bytes[i] for (var bit = 7; bit >= 0; bit--) b.write_bit((byte >> bit) & 1) } stone(b) return b } function deepCompare(expected, actual, path) { path = path || '' if (expected == actual) return { passed: true, messages: [] }; if (is_number(expected) && is_number(actual)) { if (isNaN(expected) && isNaN(actual)) return { passed: true, messages: [] }; var diff = abs(expected - actual); if (diff <= EPSILON) return { passed: true, messages: [] }; return { passed: false, messages: [ `Value mismatch at ${path}: expected ${expected}, got ${actual}`, `Difference of ${diff} is larger than tolerance ${EPSILON}` ] }; } if (is_blob(expected) && is_blob(actual)) { stone_if_needed(expected); stone_if_needed(actual) if (length(expected) != length(actual)) return { passed: false, messages: [`blob length mismatch at ${path}: ${length(expected)} vs ${length(actual)}`] } for (var i = 0; i < length(expected); 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 (is_array(expected) && is_array(actual)) { if (length(expected) != length(actual)) return { passed: false, messages: [`Array length mismatch at ${path}: expected ${length(expected)}, got ${length(actual)}`] }; var messages = []; arrfor(expected, function(val, i) { var result = deepCompare(val, actual[i], `${path}[${i}]`); if (!result.passed) messages = array(messages, result.messages) }) return { passed: length(messages) == 0, messages: messages }; } if (is_object(expected) && is_object(actual)) { var expKeys = sort(array(expected)) var actKeys = sort(array(actual)) if (JSON.stringify(expKeys) != JSON.stringify(actKeys)) return { passed: false, messages: [`Object keys mismatch at ${path}: expected ${expKeys}, got ${actKeys}`] }; var messages = []; arrfor(expKeys, function(key) { var result = deepCompare(expected[key], actual[key], `${path}.${key}`); if (!result.passed) messages = array(messages, result.messages) }) return { passed: length(messages) == 0, messages: messages }; } return { passed: false, messages: [`Value mismatch at ${path}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`] }; } function makeTest(test) { return function() { var encoded = test.replacer ? nota.encode(test.input, test.replacer) : nota.encode(test.input); if (!is_blob(encoded)){ throw "encode() should return blob"; } var decoded = test.reviver ? nota.decode(encoded, test.reviver) : nota.decode(encoded); var expected = test.expected || test.input; if (expected && (expected.private || expected.system)) { var key = expected.private ? 'private' : 'system'; expected = { [key]: expected[key] }; } var compareResult = deepCompare(expected, decoded); if (!compareResult.passed) { throw text(compareResult.messages, '; '); } }; } var testarr = [] for (var i = 0; i < 500; i++) { push(testarr, 1) } var testCases = [ { name: 'zero', input: 0 }, { name: 'positive_2023', input: 2023 }, { name: 'neg1', input: -1 }, { name: 'positive_7', input: 7 }, { name: 'neg7', input: -7 }, { name: 'positive_1023', input: 1023 }, { name: 'neg1023', input: -1023 }, { name: 'null', input: null }, { name: 'false', input: false }, { name: 'true', input: true }, { name: 'float_neg1_01', input: -1.01 }, { name: 'float_98_6', input: 98.6 }, { name: 'float_euler', input: -0.5772156649 }, { name: 'float_precision', input: -1.00000000000001 }, { name: 'float_large_neg', input: -10000000000000 }, { name: 'empty_string', input: "" }, { name: 'string_cat', input: "cat" }, { name: 'string_unicode', input: "U+1F4A9 「うんち絵文字」 «💩»" }, { name: 'buffer_ffaa', input: bytes_to_blob([0xFF, 0xAA]) }, { name: 'buffer_f0e32080', input: bytes_to_blob([0b11110000, 0b11100011, 0b00100000, 0b10000000]) }, { name: 'large_array', input: testarr }, { name: 'empty_array', input: [] }, { name: 'array_123', input: [1, 2, 3] }, { name: 'array_mixed', input: [-1, 0, 1.5] }, { name: 'empty_object', input: {} }, { name: 'object_ab', input: { a: 1, b: 2 } }, { name: 'object_nested', input: { num: 42, arr: [1, -1, 2.5], str: "test", obj: { x: true } } }, { name: 'object_private', input: { private: { address: "test" } } }, { name: 'object_system', input: { system: { msg: "hello" } } }, { name: 'array_system_nested', input: [{ system: {msg: "hello" } }, { num: 42, arr: [1, -1, 2.5], str: "test", obj: { x: true } }] }, { name: 'empty_buffer', input: blob() }, { name: 'nested_empty_array', input: [[]] }, { name: 'empty_key_value', input: { "": "" } }, { name: 'small_float', input: 1e-10 }, { name: 'replacer_multiply', input: { a: 1, b: 2 }, replacer: (key, value) => is_number(value) ? value * 2 : value, expected: { a: 2, b: 4 } }, { name: 'replacer_string_append', input: { x: "test", y: 5 }, replacer: (key, value) => key == 'x' ? value + "!" : value, expected: { x: "test!", y: 5 } }, { name: 'reviver_multiply', input: { a: 1, b: 2 }, reviver: (key, value) => is_number(value) ? value * 3 : value, expected: { a: 3, b: 6 } }, { name: 'reviver_increment', input: { x: "test", y: 10 }, reviver: (key, value) => key == 'y' ? value + 1 : value, expected: { x: "test", y: 11 } } ]; var tests = {}; for (var i = 0; i < length(testCases); i++) { var t = testCases[i]; tests[t.name] = makeTest(t); } return tests;