Source code

Revision control

Copy as Markdown

Other Tools

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*---
defines: [completesNormally, raisesException, deepEqual, makeIterator, Permutations, assertThrowsValue, assertThrownErrorContains, assertThrowsInstanceOfWithMessageCheck, assertThrowsInstanceOf, assertThrowsInstanceOfWithMessage, assertThrowsInstanceOfWithMessageContains, assertDeepEq]
allow_unused: True
---*/
(function() {
const undefined = void 0;
/*
* completesNormally(CODE) returns true if evaluating CODE (as eval
* code) completes normally (rather than throwing an exception).
*/
globalThis.completesNormally = function completesNormally(code) {
try {
eval(code);
return true;
} catch (exception) {
return false;
}
}
/*
* raisesException(EXCEPTION)(CODE) returns true if evaluating CODE (as
* eval code) throws an exception object that is an instance of EXCEPTION,
* and returns false if it throws any other error or evaluates
* successfully. For example: raises(TypeError)("0()") == true.
*/
globalThis.raisesException = function raisesException(exception) {
return function (code) {
try {
eval(code);
return false;
} catch (actual) {
return actual instanceof exception;
}
};
};
/*
* Return true if A is equal to B, where equality on arrays and objects
* means that they have the same set of enumerable properties, the values
* of each property are deep_equal, and their 'length' properties are
* equal. Equality on other types is ==.
*/
globalThis.deepEqual = function deepEqual(a, b) {
if (typeof a != typeof b)
return false;
if (typeof a == 'object') {
var props = {};
// For every property of a, does b have that property with an equal value?
for (var prop in a) {
if (!deepEqual(a[prop], b[prop]))
return false;
props[prop] = true;
}
// Are all of b's properties present on a?
for (var prop in b)
if (!props[prop])
return false;
// length isn't enumerable, but we want to check it, too.
return a.length == b.length;
}
if (a === b) {
// Distinguish 0 from -0, even though they are ===.
return a !== 0 || 1/a === 1/b;
}
// Treat NaNs as equal, even though NaN !== NaN.
// NaNs are the only non-reflexive values, i.e., if a !== a, then a is a NaN.
// isNaN is broken: it converts its argument to number, so isNaN("foo") => true
return a !== a && b !== b;
}
/** Make an iterator with a return method. */
globalThis.makeIterator = function makeIterator(overrides) {
var throwMethod;
if (overrides && overrides.throw)
throwMethod = overrides.throw;
var iterator = {
throw: throwMethod,
next: function(x) {
if (overrides && overrides.next)
return overrides.next(x);
return { done: false };
},
return: function(x) {
if (overrides && overrides.ret)
return overrides.ret(x);
return { done: true };
}
};
return function() { return iterator; };
};
/** Yield every permutation of the elements in some array. */
globalThis.Permutations = function* Permutations(items) {
if (items.length == 0) {
yield [];
} else {
items = items.slice(0);
for (let i = 0; i < items.length; i++) {
let swap = items[0];
items[0] = items[i];
items[i] = swap;
for (let e of Permutations(items.slice(1, items.length)))
yield [items[0]].concat(e);
}
}
};
if (typeof globalThis.assertThrowsValue === 'undefined') {
globalThis.assertThrowsValue = function assertThrowsValue(f, val, msg) {
var fullmsg;
try {
f();
} catch (exc) {
if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val))
return;
fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
}
if (fullmsg === undefined)
fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown";
if (msg !== undefined)
fullmsg += " - " + msg;
throw new Error(fullmsg);
};
}
if (typeof globalThis.assertThrownErrorContains === 'undefined') {
globalThis.assertThrownErrorContains = function assertThrownErrorContains(thunk, substr) {
try {
thunk();
} catch (e) {
if (e.message.indexOf(substr) !== -1)
return;
throw new Error("Expected error containing " + substr + ", got " + e);
}
throw new Error("Expected error containing " + substr + ", no exception thrown");
};
}
if (typeof globalThis.assertThrowsInstanceOfWithMessageCheck === 'undefined') {
globalThis.assertThrowsInstanceOfWithMessageCheck = function assertThrowsInstanceOfWithMessageCheck(f, ctor, check, msg) {
var fullmsg;
try {
f();
} catch (exc) {
if (!(exc instanceof ctor))
fullmsg = `Assertion failed: expected exception ${ctor.name}, got ${exc}`;
else {
var result = check(exc.message);
if (!result)
fullmsg = `Assertion failed: expected exception with message, got ${exc}`;
else
return;
}
}
if (fullmsg === undefined)
fullmsg = `Assertion failed: expected exception ${ctor.name}, no exception thrown`;
if (msg !== undefined)
fullmsg += " - " + msg;
throw new Error(fullmsg);
};
}
if (typeof globalThis.assertThrowsInstanceOf === 'undefined') {
globalThis.assertThrowsInstanceOf = function assertThrowsInstanceOf(f, ctor, msg) {
assertThrowsInstanceOfWithMessageCheck(f, ctor, _ => true, msg);
};
}
if (typeof globalThis.assertThrowsInstanceOfWithMessage === 'undefined') {
globalThis.assertThrowsInstanceOfWithMessage = function assertThrowsInstanceOfWithMessage(f, ctor, expected, msg) {
assertThrowsInstanceOfWithMessageCheck(f, ctor, message => message === expected, msg);
}
}
if (typeof globalThis.assertThrowsInstanceOfWithMessageContains === 'undefined') {
globalThis.assertThrowsInstanceOfWithMessageContains = function assertThrowsInstanceOfWithMessageContains(f, ctor, substr, msg) {
assertThrowsInstanceOfWithMessageCheck(f, ctor, message => message.indexOf(substr) !== -1, msg);
}
}
globalThis.assertDeepEq = (function(){
var call = Function.prototype.call,
Array_isArray = Array.isArray,
Array_includes = call.bind(Array.prototype.includes),
Map_ = Map,
Error_ = Error,
Symbol_ = Symbol,
Symbol_keyFor = Symbol.keyFor,
Symbol_description = call.bind(Object.getOwnPropertyDescriptor(Symbol.prototype, "description").get),
Map_has = call.bind(Map.prototype.has),
Map_get = call.bind(Map.prototype.get),
Map_set = call.bind(Map.prototype.set),
Object_toString = call.bind(Object.prototype.toString),
Function_toString = call.bind(Function.prototype.toString),
Object_getPrototypeOf = Object.getPrototypeOf,
Object_hasOwnProperty = call.bind(Object.prototype.hasOwnProperty),
Object_getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
Object_isExtensible = Object.isExtensible,
Object_getOwnPropertyNames = Object.getOwnPropertyNames;
// Return true iff ES6 Type(v) isn't Object.
// Note that `typeof document.all === "undefined"`.
function isPrimitive(v) {
return (v === null ||
v === undefined ||
typeof v === "boolean" ||
typeof v === "number" ||
typeof v === "string" ||
typeof v === "symbol");
}
function assertSameValue(a, b, msg) {
try {
assertEq(a, b);
} catch (exc) {
throw Error_(exc.message + (msg ? " " + msg : ""));
}
}
function assertSameClass(a, b, msg) {
var ac = Object_toString(a), bc = Object_toString(b);
assertSameValue(ac, bc, msg);
switch (ac) {
case "[object Function]":
if (typeof isProxy !== "undefined" && !isProxy(a) && !isProxy(b))
assertSameValue(Function_toString(a), Function_toString(b), msg);
}
}
function at(prevmsg, segment) {
return prevmsg ? prevmsg + segment : "at _" + segment;
}
// Assert that the arguments a and b are thoroughly structurally equivalent.
//
// For the sake of speed, we cut a corner:
// var x = {}, y = {}, ax = [x];
// assertDeepEq([ax, x], [ax, y]); // passes (?!)
//
// Technically this should fail, since the two object graphs are different.
// (The graph of [ax, y] contains one more object than the graph of [ax, x].)
//
// To get technically correct behavior, pass {strictEquivalence: true}.
// This is slower because we have to walk the entire graph, and Object.prototype
// is big.
//
return function assertDeepEq(a, b, options) {
var strictEquivalence = options ? options.strictEquivalence : false;
function assertSameProto(a, b, msg) {
check(Object_getPrototypeOf(a), Object_getPrototypeOf(b), at(msg, ".__proto__"));
}
function failPropList(na, nb, msg) {
throw Error_("got own properties " + JSON.stringify(na) + ", expected " + JSON.stringify(nb) +
(msg ? " " + msg : ""));
}
function assertSameProps(a, b, msg) {
var na = Object_getOwnPropertyNames(a),
nb = Object_getOwnPropertyNames(b);
if (na.length !== nb.length)
failPropList(na, nb, msg);
// Ignore differences in whether Array elements are stored densely.
if (Array_isArray(a)) {
na.sort();
nb.sort();
}
for (var i = 0; i < na.length; i++) {
var name = na[i];
if (name !== nb[i])
failPropList(na, nb, msg);
var da = Object_getOwnPropertyDescriptor(a, name),
db = Object_getOwnPropertyDescriptor(b, name);
var pmsg = at(msg, /^[_$A-Za-z0-9]+$/.test(name)
? /0|[1-9][0-9]*/.test(name) ? "[" + name + "]" : "." + name
: "[" + JSON.stringify(name) + "]");
assertSameValue(da.configurable, db.configurable, at(pmsg, ".[[Configurable]]"));
assertSameValue(da.enumerable, db.enumerable, at(pmsg, ".[[Enumerable]]"));
if (Object_hasOwnProperty(da, "value")) {
if (!Object_hasOwnProperty(db, "value"))
throw Error_("got data property, expected accessor property" + pmsg);
check(da.value, db.value, pmsg);
} else {
if (Object_hasOwnProperty(db, "value"))
throw Error_("got accessor property, expected data property" + pmsg);
check(da.get, db.get, at(pmsg, ".[[Get]]"));
check(da.set, db.set, at(pmsg, ".[[Set]]"));
}
}
};
const wellKnownSymbols = Reflect.ownKeys(Symbol)
.map(key => Symbol[key])
.filter(value => typeof value === "symbol");
// The standard doesn't offer a convenient way to distinguish well-known
// symbols from user-created symbols.
function isSimilarSymbol(a, b) {
// Fast path for same symbols.
if (a === b) {
return true;
}
// 1. Symbol descriptions must match.
// 2. Either both symbols are in the registry or none is.
// 3. Neither symbol must be a well-known symbol, because those are
// already handled through the fast path.
return Symbol_description(a) === Symbol_description(b) &&
Symbol_keyFor(a) === Symbol_keyFor(b) &&
!Array_includes(wellKnownSymbols, a) &&
!Array_includes(wellKnownSymbols, b);
}
var ab = new Map_();
var bpath = new Map_();
function check(a, b, path) {
if (typeof a === "symbol") {
// Symbols are primitives, but they have identity.
// Symbol("x") !== Symbol("x") but
// assertDeepEq(Symbol("x"), Symbol("x")) should pass.
if (typeof b !== "symbol") {
throw Error_("got " + String(a) + ", expected " + String(b) + " " + path);
} else if (!isSimilarSymbol(a, b)) {
throw Error_("got " + String(a) + ", expected " + String(b) + " " + path);
} else if (Map_has(ab, a)) {
assertSameValue(Map_get(ab, a), b, path);
} else if (Map_has(bpath, b)) {
var bPrevPath = Map_get(bpath, b) || "_";
throw Error_("got distinct symbols " + at(path, "") + " and " +
at(bPrevPath, "") + ", expected the same symbol both places");
} else {
Map_set(ab, a, b);
Map_set(bpath, b, path);
}
} else if (isPrimitive(a)) {
assertSameValue(a, b, path);
} else if (isPrimitive(b)) {
throw Error_("got " + Object_toString(a) + ", expected " + String(b) + " " + path);
} else if (Map_has(ab, a)) {
assertSameValue(Map_get(ab, a), b, path);
} else if (Map_has(bpath, b)) {
var bPrevPath = Map_get(bpath, b) || "_";
throw Error_("got distinct objects " + at(path, "") + " and " + at(bPrevPath, "") +
", expected the same object both places");
} else {
Map_set(ab, a, b);
Map_set(bpath, b, path);
if (a !== b || strictEquivalence) {
assertSameClass(a, b, path);
assertSameProto(a, b, path);
assertSameProps(a, b, path);
assertSameValue(Object_isExtensible(a),
Object_isExtensible(b),
at(path, ".[[Extensible]]"));
}
}
}
check(a, b, "");
};
})();
})();