// // 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: "make() should produce an empty antestone blob of length 0", run() { let b = blob.make(); let isBlob = blob["isblob"](b); let length = blob.length(b); let passed = (isBlob === true && length === 0); let messages = []; if (!isBlob) 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: "make(16) should create a blob with capacity >=16 bits and length=0", run() { let b = blob.make(16); let isBlob = blob["isblob"](b); let length = blob.length(b); 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) { name: "make(5, true) should create a blob of length=5 bits, all 1s (antestone)", run() { let b = blob.make(5, true); let len = blob.length(b); if (len !== 5) { return { passed: false, messages: [`Expected length=5, got ${len}`] }; } // Check bits for (let i = 0; i < 5; i++) { let bitVal = blob.read_logical(b, 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 { name: "write_bit() on an empty blob, then read_logical() to verify bits", run() { let b = blob.make(); // starts length=0 // write bits: true, false, true blob.write_bit(b, true); // bit #0 blob.write_bit(b, false); // bit #1 blob.write_bit(b, true); // bit #2 let len = blob.length(b); if (len !== 3) { return { passed: false, messages: [`Expected length=3, got ${len}`] }; } let bits = [ blob.read_logical(b, 0), blob.read_logical(b, 1), blob.read_logical(b, 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 = blob.make(5, false); // Stone it blob.stone(b); // Try to write let passed = true; let messages = []; try { blob.write_bit(b, 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 { name: "make(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 = blob.make(); for (let i = 0; i < 10; i++) { blob.write_bit(original, 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 = blob.make(original, 2, 7); let len = blob.length(copy); if (len !== 5) { return { passed: false, messages: [`Expected length=5, got ${len}`] }; } let bits = []; for (let i = 0; i < len; i++) { bits.push(blob.read_logical(copy, i)); } let compare = deepCompare([true, false, true, false, true], bits); return compare; } }, // 7) Checking isblob(something) { name: "isblob should correctly identify blob vs. non-blob", run() { let b = blob.make(); let isB = blob["isblob"](b); let isNum = blob["isblob"](42); let isObj = blob["isblob"]({ length: 3 }); let passed = (isB === true && isNum === false && isObj === false); let messages = []; if (!passed) { messages.push(`Expected isblob(b)=true, isblob(42)=false, isblob({})=false; got ${isB}, ${isNum}, ${isObj}`); } return { passed, messages }; } } ]; // --------------------------------------------------------------------------- // 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"; console.log(`${r.testName} - ${status}`); if (!r.passed && r.messages.length > 0) { console.log(" " + r.messages.join("\n ")); } if (r.passed) passedCount++; } console.log(`\nResult: ${passedCount}/${results.length} tests passed`); if (passedCount < results.length) { console.log("Overall: FAILED"); if (os && os.exit) os.exit(1); } else { console.log("Overall: PASSED"); if (os && os.exit) os.exit(0); }