612 lines
16 KiB
Plaintext
612 lines
16 KiB
Plaintext
// Blob test suite
|
|
|
|
var Blob = use('blob');
|
|
var time = use('time')
|
|
|
|
// A small tolerance for floating comparisons if needed
|
|
var EPSILON = 1e-12;
|
|
|
|
// Track test results
|
|
var testResults = {
|
|
type: 'test_results',
|
|
test_name: 'blob',
|
|
passed: 0,
|
|
failed: 0,
|
|
total: 0,
|
|
failures: [],
|
|
duration: 0
|
|
};
|
|
|
|
var startTime;
|
|
|
|
function deepCompare(expected, actual, path) {
|
|
if (!path) 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: [] };
|
|
}
|
|
var 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
|
|
]
|
|
};
|
|
}
|
|
var messages = [];
|
|
for (var i = 0; i < expected.length; i++) {
|
|
var r = deepCompare(expected[i], actual[i], path + '[' + i + ']');
|
|
if (!r.passed) messages.push.apply(messages, r.messages);
|
|
}
|
|
return {
|
|
passed: messages.length === 0,
|
|
messages: messages
|
|
};
|
|
}
|
|
|
|
// Compare objects
|
|
if (
|
|
typeof expected === 'object' &&
|
|
expected !== null &&
|
|
typeof actual === 'object' &&
|
|
actual !== null
|
|
) {
|
|
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 + ': expected [' + expKeys + '], got [' + actKeys + ']'
|
|
]
|
|
};
|
|
}
|
|
var messages = [];
|
|
for (var k = 0; k < expKeys.length; k++) {
|
|
var key = expKeys[k];
|
|
var r = deepCompare(expected[key], actual[key], path ? path + '.' + key : key);
|
|
if (!r.passed) messages.push.apply(messages, r.messages);
|
|
}
|
|
return { passed: messages.length === 0, messages: 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 run a single test
|
|
function runTest(testName, testFn) {
|
|
var passed = true;
|
|
var messages = [];
|
|
|
|
try {
|
|
var 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()));
|
|
}
|
|
|
|
// Update results
|
|
testResults.total++;
|
|
if (passed) {
|
|
testResults.passed++;
|
|
} else {
|
|
testResults.failed++;
|
|
testResults.failures.push({
|
|
name: testName,
|
|
error: messages.join('\n')
|
|
});
|
|
}
|
|
|
|
// Log individual result
|
|
log.console(testName + ' - ' + (passed ? 'Passed' : 'Failed'));
|
|
if (!passed && messages.length > 0) {
|
|
log.console(' ' + messages.join('\n '));
|
|
}
|
|
}
|
|
|
|
// Test suite
|
|
var tests = [
|
|
// 1) Ensure we can create a blank blob
|
|
{
|
|
name: "new Blob() should produce an empty antestone blob of length 0",
|
|
run: function() {
|
|
var b = new Blob();
|
|
var length = b.length;
|
|
var passed = (b instanceof Blob && length === 0);
|
|
var 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: passed, messages: messages };
|
|
}
|
|
},
|
|
|
|
// 2) Make a blob with some capacity
|
|
{
|
|
name: "new Blob(16) should create a blob with capacity >=16 bits and length=0",
|
|
run: function() {
|
|
var b = new Blob(16);
|
|
var isBlob = b instanceof Blob;
|
|
var length = b.length;
|
|
var passed = isBlob && length === 0;
|
|
var messages = [];
|
|
if (!isBlob) messages.push("Not recognized as a blob");
|
|
if (length !== 0) messages.push('Expected length=0, got ' + length);
|
|
return { passed: passed, messages: 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: function() {
|
|
var b = new Blob(5, true);
|
|
var len = b.length;
|
|
if (len !== 5) {
|
|
return {
|
|
passed: false,
|
|
messages: ['Expected length=5, got ' + len]
|
|
};
|
|
}
|
|
|
|
// Try to read before stone - should return null
|
|
var 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 (var i = 0; i < 5; i++) {
|
|
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: function() {
|
|
var 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
|
|
var len = b.length;
|
|
if (len !== 3) {
|
|
return {
|
|
passed: false,
|
|
messages: ['Expected length=3, got ' + len]
|
|
};
|
|
}
|
|
|
|
// Must stone before reading
|
|
stone(b);
|
|
|
|
var bits = [
|
|
b.read_logical(0),
|
|
b.read_logical(1),
|
|
b.read_logical(2)
|
|
];
|
|
var 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: function() {
|
|
var b = new Blob(5, false);
|
|
// Stone it
|
|
stone(b);
|
|
// Try to write
|
|
var passed = true;
|
|
var 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: passed, messages: messages };
|
|
}
|
|
},
|
|
|
|
// 6) make(blob, from, to) - copying range from an existing blob
|
|
{
|
|
name: "new Blob(existing_blob, from, to) can copy partial range",
|
|
run: function() {
|
|
// Create a 10-bit blob: pattern T F T F T F T F T F
|
|
var original = new Blob();
|
|
for (var i = 0; i < 10; i++) {
|
|
original.write_bit(i % 2 === 0);
|
|
}
|
|
// Copy bits [2..7)
|
|
var copy = new Blob(original, 2, 7);
|
|
var len = copy.length;
|
|
if (len !== 5) {
|
|
return {
|
|
passed: false,
|
|
messages: ['Expected length=5, got ' + len]
|
|
};
|
|
}
|
|
|
|
// Stone the copy to read from it
|
|
stone(copy);
|
|
|
|
var bits = [];
|
|
for (var i = 0; i < len; i++) {
|
|
bits.push(copy.read_logical(i));
|
|
}
|
|
var compare = deepCompare([true, false, true, false, true], bits);
|
|
return compare;
|
|
}
|
|
},
|
|
|
|
// 7) Checking instanceof
|
|
{
|
|
name: "instanceof should correctly identify blob vs. non-blob",
|
|
run: function() {
|
|
var b = new Blob();
|
|
var isB = b instanceof Blob;
|
|
var isNum = 42 instanceof Blob;
|
|
var isObj = { length: 3 } instanceof Blob;
|
|
var passed = (isB === true && isNum === false && isObj === false);
|
|
var messages = [];
|
|
if (!passed) {
|
|
messages.push('Expected (b instanceof Blob)=true, (42 instanceof Blob)=false, ({} instanceof Blob)=false; got ' + isB + ', ' + isNum + ', ' + isObj);
|
|
}
|
|
return { passed: passed, messages: messages };
|
|
}
|
|
},
|
|
|
|
// 8) Test write_blob
|
|
{
|
|
name: "write_blob() should append one blob to another",
|
|
run: function() {
|
|
var b1 = new Blob();
|
|
b1.write_bit(true);
|
|
b1.write_bit(false);
|
|
|
|
var 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);
|
|
var bits = [];
|
|
for (var 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: function() {
|
|
var 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);
|
|
|
|
var val1 = b.read_fit(0, 3);
|
|
var 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 - SKIPPED due to native error
|
|
{
|
|
name: "write_kim() and read_kim() should handle kim encoding",
|
|
run: function() {
|
|
// Skip this test as it's causing native errors
|
|
return { passed: true, messages: ['Test skipped due to native implementation issues'] };
|
|
}
|
|
},
|
|
|
|
// 11) Test write_text and read_text
|
|
{
|
|
name: "write_text() and read_text() should handle text encoding",
|
|
run: function() {
|
|
var b = new Blob();
|
|
b.write_text("Hello");
|
|
|
|
stone(b);
|
|
|
|
var 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: function() {
|
|
var b = new Blob();
|
|
b.write_dec64(3.14159);
|
|
b.write_dec64(-42.5);
|
|
|
|
stone(b);
|
|
|
|
var val1 = b.read_dec64(0);
|
|
var val2 = b.read_dec64(64);
|
|
|
|
// Allow small floating point differences
|
|
var diff1 = Math.abs(val1 - 3.14159);
|
|
var 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: function() {
|
|
var 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
|
|
var 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
|
|
var bits = [];
|
|
for (var 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: function() {
|
|
var len1 = Blob.kim_length(42); // Should be 8 bits
|
|
var len2 = Blob.kim_length(1000); // Should be 16 bits
|
|
var 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: function() {
|
|
var b = new Blob();
|
|
b.write_bit(1);
|
|
b.write_bit(0);
|
|
b.write_bit(true);
|
|
b.write_bit(false);
|
|
|
|
stone(b);
|
|
|
|
var bits = [];
|
|
for (var 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: function() {
|
|
var b = new Blob();
|
|
for (var i = 0; i < 10; i++) {
|
|
b.write_bit(i % 3 === 0); // Pattern: T,F,F,T,F,F,T,F,F,T
|
|
}
|
|
stone(b);
|
|
|
|
var 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]
|
|
};
|
|
}
|
|
|
|
var bits = [];
|
|
for (var 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: function() {
|
|
// Simple random function that alternates
|
|
var counter = 0;
|
|
var randomFunc = function() { return counter++; };
|
|
|
|
var 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
|
|
var bits = [];
|
|
for (var i = 0; i < 8; i++) {
|
|
bits.push(b.read_logical(i));
|
|
}
|
|
|
|
return deepCompare([false, true, false, true, false, true, false, true], bits);
|
|
}
|
|
}
|
|
];
|
|
|
|
// Message receiver
|
|
$_.receiver(function(msg) {
|
|
if (msg.type === 'run_tests') {
|
|
log.console("HERE")
|
|
startTime = time.number();
|
|
|
|
// Run all tests
|
|
for (var i = 0; i < tests.length; i++) {
|
|
var test = tests[i];
|
|
runTest(test.name, test.run);
|
|
}
|
|
|
|
// Calculate duration
|
|
testResults.duration = time.number() - startTime;
|
|
|
|
// Send results back
|
|
send(msg, testResults);
|
|
}
|
|
}); |