// // test_blob.js // // Example test script for qjs_blob.c/h // // Run in QuickJS, e.g. // qjs -m test_blob.js // // Attempt to "use" the blob module as if it was installed or compiled in. var Blob = use('blob') // If you're testing in an environment without a 'use' loader, you might do // something like importing the compiled C module or linking it differently. // (Optional) if you have an 'os' module available for controlling exit codes: var os = undefined; try { os = use('os'); } catch (e) { // If there's no 'os' module, ignore } // A small tolerance for floating comparisons if needed var EPSILON = 1e-12; function deepCompare(expected, actual, path = '') { // Basic triple-equals check if (expected === actual) { return { passed: true, messages: [] }; } // Compare booleans if (typeof expected === 'boolean' && typeof actual === 'boolean') { return { passed: false, messages: [ `Boolean mismatch at ${path}: expected ${expected}, got ${actual}` ] }; } // Compare numbers with tolerance if (typeof expected === 'number' && typeof actual === 'number') { if (isNaN(expected) && isNaN(actual)) { return { passed: true, messages: [] }; } const diff = Math.abs(expected - actual); if (diff <= EPSILON) { return { passed: true, messages: [] }; } return { passed: false, messages: [ `Number mismatch at ${path}: expected ${expected}, got ${actual}`, `Difference of ${diff} > EPSILON (${EPSILON})` ] }; } // Compare arrays if (Array.isArray(expected) && Array.isArray(actual)) { if (expected.length !== actual.length) { return { passed: false, messages: [ `Array length mismatch at ${path}: expected len=${expected.length}, got len=${actual.length}` ] }; } let messages = []; for (let i = 0; i < expected.length; i++) { let r = deepCompare(expected[i], actual[i], `${path}[${i}]`); if (!r.passed) messages.push(...r.messages); } return { passed: messages.length === 0, messages }; } // Compare objects if ( typeof expected === 'object' && expected !== null && typeof actual === 'object' && actual !== null ) { let expKeys = Object.keys(expected).sort(); let actKeys = Object.keys(actual).sort(); if (JSON.stringify(expKeys) !== JSON.stringify(actKeys)) { return { passed: false, messages: [ `Object keys mismatch at ${path}: expected [${expKeys}], got [${actKeys}]` ] }; } let messages = []; for (let k of expKeys) { let r = deepCompare(expected[k], actual[k], path ? path + '.' + k : k); if (!r.passed) messages.push(...r.messages); } return { passed: messages.length === 0, messages }; } // If none of the above, treat as a mismatch return { passed: false, messages: [ `Mismatch at ${path}: expected ${JSON.stringify( expected )}, got ${JSON.stringify(actual)}` ] }; } // Helper to record the results of a single test function runTest(testName, testFn) { let passed = true, messages = []; try { const result = testFn(); if (typeof result === 'object' && result !== null) { passed = result.passed; messages = result.messages || []; } else { // If the testFn returned a boolean or no return, interpret it passed = !!result; } } catch (e) { passed = false; messages.push(`Exception thrown: ${e.stack || e.toString()}`); } return { testName, passed, messages }; } // --------------------------------------------------------------------------- // The test suite // --------------------------------------------------------------------------- let tests = [ // 1) Ensure we can create a blank blob { name: "new Blob() should produce an empty antestone blob of length 0", run() { let b = new Blob(); let length = b.length; let passed = (b instanceof Blob && length === 0); log.console(`blob len: ${b.length}, is blob? ${b instanceof Blob}`) let messages = []; if (!(b instanceof Blob)) messages.push("Returned object is not recognized as a blob"); if (length !== 0) messages.push(`Expected length 0, got ${length}`); return { passed, messages }; } }, // 2) Make a blob with some capacity { name: "new Blob(16) should create a blob with capacity >=16 bits and length=0", run() { let b = new Blob(16); let isBlob = b instanceof Blob; let length = b.length; let passed = isBlob && length === 0; let messages = []; if (!isBlob) messages.push("Not recognized as a blob"); if (length !== 0) messages.push(`Expected length=0, got ${length}`); return { passed, messages }; } }, // 3) Make a blob with (length, logical) - but can't read until stone { name: "new Blob(5, true) should create a blob of length=5 bits, all 1s - needs stone to read", run() { let b = new Blob(5, true); let len = b.length; if (len !== 5) { return { passed: false, messages: [`Expected length=5, got ${len}`] }; } // Try to read before stone - should return null let bitVal = b.read_logical(0); if (bitVal !== null) { return { passed: false, messages: [`Expected null when reading antestone blob, got ${bitVal}`] }; } // Stone it stone(b) // Now check bits for (let i = 0; i < 5; i++) { let bitVal = b.read_logical(i); if (bitVal !== true) { return { passed: false, messages: [`Bit at index ${i} expected true, got ${bitVal}`] }; } } return { passed: true, messages: [] }; } }, // 4) Write bits to an empty blob, then stone and read { name: "write_bit() on an empty blob, then stone and read_logical() to verify bits", run() { let b = new Blob(); // starts length=0 // write bits: true, false, true b.write_bit(true); // bit #0 b.write_bit(false); // bit #1 b.write_bit(true); // bit #2 let len = b.length; if (len !== 3) { return { passed: false, messages: [`Expected length=3, got ${len}`] }; } // Must stone before reading stone(b) let bits = [ b.read_logical(0), b.read_logical(1), b.read_logical(2) ]; let compare = deepCompare([true, false, true], bits); return compare; } }, // 5) Stone a blob, then attempt to write -> fail { name: "Stoning a blob should prevent further writes", run() { let b = new Blob(5, false); // Stone it stone(b) // Try to write let passed = true; let messages = []; try { b.write_bit(true); passed = false; messages.push("Expected an error or refusal when writing to a stone blob, but none occurred"); } catch (e) { // We expect an exception or some error scenario } return { passed, messages }; } }, // 6) make(blob, from, to) - copying range from an existing blob (copy doesn't need source to be stone) { name: "new Blob(existing_blob, from, to) can copy partial range", run() { // Create a 10-bit blob: pattern T F T F T F T F T F let original = new Blob(); for (let i = 0; i < 10; i++) { original.write_bit(i % 2 === 0); } // Copy bits [2..7) // That slice is bits #2..6: T, F, T, F, T // indices: 2: T(1), 3: F(0), 4: T(1), 5: F(0), 6: T(1) // so length=5 let copy = new Blob(original, 2, 7); let len = copy.length; if (len !== 5) { return { passed: false, messages: [`Expected length=5, got ${len}`] }; } // Stone the copy to read from it stone(copy) let bits = []; for (let i = 0; i < len; i++) { bits.push(copy.read_logical(i)); } let compare = deepCompare([true, false, true, false, true], bits); return compare; } }, // 7) Checking instanceof { name: "instanceof should correctly identify blob vs. non-blob", run() { let b = new Blob(); let isB = b instanceof Blob; let isNum = 42 instanceof Blob; let isObj = { length: 3 } instanceof Blob; let passed = (isB === true && isNum === false && isObj === false); let messages = []; if (!passed) { messages.push(`Expected (b instanceof Blob)=true, (42 instanceof Blob)=false, ({} instanceof Blob)=false; got ${isB}, ${isNum}, ${isObj}`); } return { passed, messages }; } }, // 8) Test write_blob { name: "write_blob() should append one blob to another", run() { let b1 = new Blob(); b1.write_bit(true); b1.write_bit(false); let b2 = new Blob(); b2.write_bit(true); b2.write_bit(true); b1.write_blob(b2); if (b1.length !== 4) { return { passed: false, messages: [`Expected length=4 after write_blob, got ${b1.length}`] }; } stone(b1) let bits = []; for (let i = 0; i < 4; i++) { bits.push(b1.read_logical(i)); } return deepCompare([true, false, true, true], bits); } }, // 9) Test write_fit and read_fit { name: "write_fit() and read_fit() should handle fixed-size bit fields", run() { let b = new Blob(); b.write_fit(5, 3); // Write value 5 in 3 bits (101) b.write_fit(7, 4); // Write value 7 in 4 bits (0111) if (b.length !== 7) { return { passed: false, messages: [`Expected length=7, got ${b.length}`] }; } stone(b) let val1 = b.read_fit(0, 3); let val2 = b.read_fit(3, 4); if (val1 !== 5 || val2 !== 7) { return { passed: false, messages: [`Expected read_fit to return 5 and 7, got ${val1} and ${val2}`] }; } return { passed: true, messages: [] }; } }, // 10) Test write_kim and read_kim { name: "write_kim() and read_kim() should handle kim encoding", run() { let b = new Blob(); b.write_kim(42); // Small positive number b.write_kim(-1); // Small negative number b.write_kim(1000); // Larger number stone(b) let result1 = b.read_kim(0); let result2 = b.read_kim(result1.bits_read); let result3 = b.read_kim(result1.bits_read + result2.bits_read); if (result1.value !== 42 || result2.value !== -1 || result3.value !== 1000) { return { passed: false, messages: [`Expected kim values 42, -1, 1000, got ${result1.value}, ${result2.value}, ${result3.value}`] }; } return { passed: true, messages: [] }; } }, // 11) Test write_text and read_text { name: "write_text() and read_text() should handle text encoding", run() { let b = new Blob(); b.write_text("Hello"); stone(b) let result = b.read_text(0); if (result.text !== "Hello") { return { passed: false, messages: [`Expected text "Hello", got "${result.text}"`] }; } return { passed: true, messages: [] }; } }, // 12) Test write_dec64 and read_dec64 { name: "write_dec64() and read_dec64() should handle decimal encoding", run() { let b = new Blob(); b.write_dec64(3.14159); b.write_dec64(-42.5); stone(b) let val1 = b.read_dec64(0); let val2 = b.read_dec64(64); // Allow small floating point differences let diff1 = Math.abs(val1 - 3.14159); let diff2 = Math.abs(val2 - (-42.5)); if (diff1 > EPSILON || diff2 > EPSILON) { return { passed: false, messages: [`Expected dec64 values 3.14159 and -42.5, got ${val1} and ${val2}`] }; } return { passed: true, messages: [] }; } }, // 13) Test write_pad and pad? { name: "write_pad() and pad?() should handle block padding", run() { let b = new Blob(); b.write_bit(true); b.write_bit(false); b.write_bit(true); // Length is now 3 b.write_pad(8); // Pad to multiple of 8 if (b.length !== 8) { return { passed: false, messages: [`Expected length=8 after padding, got ${b.length}`] }; } stone(b) // Check pad? function let isPadded = b["pad?"](3, 8); if (!isPadded) { return { passed: false, messages: [`Expected pad?(3, 8) to return true`] }; } // Verify padding pattern: original bits, then 1, then 0s let bits = []; for (let i = 0; i < 8; i++) { bits.push(b.read_logical(i)); } return deepCompare([true, false, true, true, false, false, false, false], bits); } }, // 14) Test Blob.kim_length static function { name: "Blob.kim_length() should calculate correct kim encoding lengths", run() { let len1 = Blob.kim_length(42); // Should be 8 bits let len2 = Blob.kim_length(1000); // Should be 16 bits let len3 = Blob.kim_length("Hello"); // 8 bits for length + 8*5 for chars = 48 if (len1 !== 8) { return { passed: false, messages: [`Expected kim_length(42)=8, got ${len1}`] }; } if (len2 !== 16) { return { passed: false, messages: [`Expected kim_length(1000)=16, got ${len2}`] }; } if (len3 !== 48) { return { passed: false, messages: [`Expected kim_length("Hello")=48, got ${len3}`] }; } return { passed: true, messages: [] }; } }, // 15) Test write_bit with numeric 0 and 1 { name: "write_bit() should accept 0, 1, true, false", run() { let b = new Blob(); b.write_bit(1); b.write_bit(0); b.write_bit(true); b.write_bit(false); stone(b) let bits = []; for (let i = 0; i < 4; i++) { bits.push(b.read_logical(i)); } return deepCompare([true, false, true, false], bits); } }, // 16) Test read_blob to create copies { name: "read_blob() should create partial copies of stone blobs", run() { let b = new Blob(); for (let i = 0; i < 10; i++) { b.write_bit(i % 3 === 0); // Pattern: T,F,F,T,F,F,T,F,F,T } stone(b) let copy = b.read_blob(3, 7); // Extract bits 3-6 stone(copy) if (copy.length !== 4) { return { passed: false, messages: [`Expected copy length=4, got ${copy.length}`] }; } let bits = []; for (let i = 0; i < 4; i++) { bits.push(copy.read_logical(i)); } // Bits 3-6 from original: T,F,F,T return deepCompare([true, false, false, true], bits); } }, // 17) Test random blob creation { name: "new Blob(length, random_func) should create random blob", run() { // Simple random function that alternates let counter = 0; let randomFunc = () => counter++; let b = new Blob(8, randomFunc); stone(b) if (b.length !== 8) { return { passed: false, messages: [`Expected length=8, got ${b.length}`] }; } // Check pattern matches counter LSB: 0,1,0,1,0,1,0,1 let bits = []; for (let i = 0; i < 8; i++) { bits.push(b.read_logical(i)); } return deepCompare([false, true, false, true, false, true, false, true], bits); } } ]; // --------------------------------------------------------------------------- // Run all tests // --------------------------------------------------------------------------- let results = []; for (let i = 0; i < tests.length; i++) { let { name, run } = tests[i]; let result = runTest(name, run); results.push(result); } // Print results let passedCount = 0; for (let r of results) { let status = r.passed ? "Passed" : "Failed"; log.console(`${r.testName} - ${status}`); if (!r.passed && r.messages.length > 0) { log.console(" " + r.messages.join("\n ")); } if (r.passed) passedCount++; } log.console(`\nResult: ${passedCount}/${results.length} tests passed`); if (passedCount < results.length) { log.console("Overall: FAILED"); if (os && os.exit) os.exit(1); } else { log.console("Overall: PASSED"); if (os && os.exit) os.exit(0); }