Some checks failed
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
322 lines
9.0 KiB
JavaScript
322 lines
9.0 KiB
JavaScript
//
|
|
// 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);
|
|
}
|