Source code

Revision control

Copy as Markdown

Other Tools

<!doctype html>
<script src="/resources/channel.sub.js"></script>
<script src="serialize-data.js"></script>
<script>
let lastData;
// Hack: these will be converted into testharness AssertionError instances in the test
// This means they will be treated identically to asserts raised by `assert_` in the harness
// In the long term we want to be able to use parts of testharness.js directly in remote
// contexts and automatically send the results over a channel.
function AssertionError(message) {
this.message = message;
}
AssertionError.prototype = Object.create(Error.prototype);
function compareResult(name, actual) {
let obj = objects[name];
// If there's an output property use that, otherwise assume the output is equal to the input
let expected = obj.hasOwnProperty("output") ? obj.output : obj.input;
seen = new Set();
try {
compareValue(actual, expected, seen);
} catch(e) {
throw new AssertionError(e.message);
}
return true;
}
function compareValue(actualValue, expectedValue, seen) {
let seenActual;
if (typeof actualValue != typeof expectedValue) {
throw new Error(`Types differ, expected ${typeof expectedValue}, got ${typeof actualValue}`);
}
if (["undefined", "string", "boolean", "number", "bigint"].includes(typeof expectedValue) ||
actualValue === null) {
if (!Object.is(actualValue, expectedValue)) {
throw new Error(`Expected ${typeof expected} ${expected}, got ${actual}`);
}
return;
}
if (expectedValue.constructor && actualValue.constructor && expectedValue.constructor.name !== actualValue.constructor.name) {
throw new Error(`Constructors differ, expected ${expectedValue.constructor.name}, got ${actualValue.constructor.name}`);
}
if (expectedValue.constructor && expectedValue.constructor.name === "SendChannel") {
if (expectedValue.uuid !== actualValue.uuid) {
throw new Error(`SendChannels differ, expected uuid ${expectedValue.uuid}, got ${actualValue.uuid}`);
}
}
else if (expectedValue.constructor && expectedValue.constructor.name === "RegExp") {
if (expectedValue.source !== actualValue.source ||
expectedValue.flags !== actualValue.flags) {
throw new Error(`RegExps differ, expected ${expectedValue}, got ${actualValue}`);
}
} else if (expectedValue.constructor && expectedValue.constructor.name == "Date") {
if (expectedValue.valueOf() !== actualValue.valueOf()) {
throw new Error(`Dates differ, expected ${expectedValue.valueOf()} (${expectedValue.toDateString()}), `
`got ${actualValue.valueOf()} (${actualValue.toDateString()})`);
}
} else if (expectedValue instanceof Error) {
if (expectedValue.message !== actualValue.message ||
expectedValue.lineNumber !== actualValue.lineNumber ||
expectedValue.columnNumber !== actualValue.columnNumber ||
expectedValue.fileName !== actualValue.fileName) {
throw new Error(`Errors differ, expected ${expectedValue}, got ${actualValue}`);
}
} else if (Array.isArray(expectedValue)) {
seenActual = seen.has(actualValue);
seenExpected = seen.has(expectedValue)
if (seenActual && seenExpected) {
return;
} else if (seenExpected && !seenActual) {
throw new Error(`Expected cyclic array`);
} else if (!seenExpected && seenActual) {
throw new Error(`Got unexpected cyclic array`);
}
seen.add(actualValue);
seen.add(expectedValue);
if (actualValue.length !== expectedValue.length) {
throw new Error(`Array lengths differ, expected ${expectedValue.length}, got ${actualValue.length}`);
}
for (let i=0; i<actualValue.length; i++) {
compareValue(actualValue[i], expectedValue[i], seen);
}
} else if (expectedValue.constructor && expectedValue.constructor.name === "Set") {
seenActual = seen.has(actualValue);
seenExpected = seen.has(expectedValue)
if (seenActual && seenExpected) {
return;
} else if (seenExpected && !seenActual) {
throw new Error(`Expected cyclic set`);
} else if (!seenExpected && seenActual) {
throw new Error(`Got unexpected cyclic set`);
}
seen.add(actualValue);
seen.add(expectedValue);
if (actualValue.size !== expectedValue.size) {
throw new Error(`Set sizes differ, expected ${expectedValue.size}, got ${actualValue.size}`);
}
// For an arbitary set it's complex to check if two sets are equivalent, since
// we'd need to compare every object in one set with every object in the
// other set, so we end up with quadratic complexity. Instead, just support sets
// containing primitives and rely on the other tests for correct handling of
// objects.
for (let entry of expectedValue) {
if (["undefined", "string", "boolean", "number", "bigint"].includes(typeof entry) || entry === null) {
if(!actualValue.has(entry)) {
throw new Error(`Set missing entry, expected ${entry}`);
}
} else {
throw new Error(`Can't compare non-primitive value ${entry} inside sets`);
}
}
} else if (expectedValue.constructor && expectedValue.constructor.name === "Map") {
seenActual = seen.has(actualValue);
seenExpected = seen.has(expectedValue)
if (seenActual && seenExpected) {
return;
} else if (seenExpected && !seenActual) {
throw new Error(`Expected cyclic map`);
} else if (!seenExpected && seenActual) {
throw new Error(`Got unexpected cyclic map`);
}
seen.add(actualValue);
seen.add(expectedValue);
if (actualValue.size !== expectedValue.size) {
throw new Error(`Map sizes differ, expected ${expectedValue.size}, got ${actualValue.size}`);
}
// So for a set we can't really check if the values are the same
// except where they're primitives
for (let [key, value] of expectedValue.entries()) {
if(!actualValue.has(key)) {
throw new Error(`Map missing key, expected key ${key} with value ${value}`);
}
compareValue(actualValue.get(key), value, seen);
}
} else {
seenActual = seen.has(actualValue);
seenExpected = seen.has(expectedValue)
if (seenActual && seenExpected) {
return;
} else if (seenExpected && !seenActual) {
throw new Error(`Expected cyclic object`);
} else if (!seenExpected && seenActual) {
throw new Error(`Got unexpected cyclic object`);
}
seen.add(actualValue);
seen.add(expectedValue);
// Compare as a general Object
let expectedEntries = Object.entries(expectedValue);
if (Object.keys(actualValue).length !== expectedEntries.length) {
throw new Error(`Object keys differ, expected [${Object.keys(expectedValue).join(",")}], got [${Object.keys(actualValue).join(",")}]`);
}
// So for a set we can't really check if the values are the same
// except where they're primitives
for (let [name, entry] of expectedEntries) {
if(!actualValue.hasOwnProperty(name)) {
throw new Error(`Object missing key ${name}`);
}
compareValue(actualValue[name], entry, seen);
}
}
}
ctx = start_global_channel();
</script>