shell.js (14108B)
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ 2 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /*--- 8 defines: [completesNormally, raisesException, deepEqual, makeIterator, Permutations, assertThrowsValue, assertThrownErrorContains, assertThrowsInstanceOfWithMessageCheck, assertThrowsInstanceOf, assertThrowsInstanceOfWithMessage, assertThrowsInstanceOfWithMessageContains, assertDeepEq] 9 allow_unused: True 10 ---*/ 11 12 (function() { 13 const undefined = void 0; 14 15 /* 16 * completesNormally(CODE) returns true if evaluating CODE (as eval 17 * code) completes normally (rather than throwing an exception). 18 */ 19 globalThis.completesNormally = function completesNormally(code) { 20 try { 21 eval(code); 22 return true; 23 } catch (exception) { 24 return false; 25 } 26 } 27 28 /* 29 * raisesException(EXCEPTION)(CODE) returns true if evaluating CODE (as 30 * eval code) throws an exception object that is an instance of EXCEPTION, 31 * and returns false if it throws any other error or evaluates 32 * successfully. For example: raises(TypeError)("0()") == true. 33 */ 34 globalThis.raisesException = function raisesException(exception) { 35 return function (code) { 36 try { 37 eval(code); 38 return false; 39 } catch (actual) { 40 return actual instanceof exception; 41 } 42 }; 43 }; 44 45 /* 46 * Return true if A is equal to B, where equality on arrays and objects 47 * means that they have the same set of enumerable properties, the values 48 * of each property are deep_equal, and their 'length' properties are 49 * equal. Equality on other types is ==. 50 */ 51 globalThis.deepEqual = function deepEqual(a, b) { 52 if (typeof a != typeof b) 53 return false; 54 55 if (typeof a == 'object') { 56 var props = {}; 57 // For every property of a, does b have that property with an equal value? 58 for (var prop in a) { 59 if (!deepEqual(a[prop], b[prop])) 60 return false; 61 props[prop] = true; 62 } 63 // Are all of b's properties present on a? 64 for (var prop in b) 65 if (!props[prop]) 66 return false; 67 // length isn't enumerable, but we want to check it, too. 68 return a.length == b.length; 69 } 70 71 if (a === b) { 72 // Distinguish 0 from -0, even though they are ===. 73 return a !== 0 || 1/a === 1/b; 74 } 75 76 // Treat NaNs as equal, even though NaN !== NaN. 77 // NaNs are the only non-reflexive values, i.e., if a !== a, then a is a NaN. 78 // isNaN is broken: it converts its argument to number, so isNaN("foo") => true 79 return a !== a && b !== b; 80 } 81 82 /** Make an iterator with a return method. */ 83 globalThis.makeIterator = function makeIterator(overrides) { 84 var throwMethod; 85 if (overrides && overrides.throw) 86 throwMethod = overrides.throw; 87 var iterator = { 88 throw: throwMethod, 89 next: function(x) { 90 if (overrides && overrides.next) 91 return overrides.next(x); 92 return { done: false }; 93 }, 94 return: function(x) { 95 if (overrides && overrides.ret) 96 return overrides.ret(x); 97 return { done: true }; 98 } 99 }; 100 101 return function() { return iterator; }; 102 }; 103 104 /** Yield every permutation of the elements in some array. */ 105 globalThis.Permutations = function* Permutations(items) { 106 if (items.length == 0) { 107 yield []; 108 } else { 109 items = items.slice(0); 110 for (let i = 0; i < items.length; i++) { 111 let swap = items[0]; 112 items[0] = items[i]; 113 items[i] = swap; 114 for (let e of Permutations(items.slice(1, items.length))) 115 yield [items[0]].concat(e); 116 } 117 } 118 }; 119 120 if (typeof globalThis.assertThrowsValue === 'undefined') { 121 globalThis.assertThrowsValue = function assertThrowsValue(f, val, msg) { 122 var fullmsg; 123 try { 124 f(); 125 } catch (exc) { 126 if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val)) 127 return; 128 fullmsg = "Assertion failed: expected exception " + val + ", got " + exc; 129 } 130 if (fullmsg === undefined) 131 fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown"; 132 if (msg !== undefined) 133 fullmsg += " - " + msg; 134 throw new Error(fullmsg); 135 }; 136 } 137 138 if (typeof globalThis.assertThrownErrorContains === 'undefined') { 139 globalThis.assertThrownErrorContains = function assertThrownErrorContains(thunk, substr) { 140 try { 141 thunk(); 142 } catch (e) { 143 if (e.message.indexOf(substr) !== -1) 144 return; 145 throw new Error("Expected error containing " + substr + ", got " + e); 146 } 147 throw new Error("Expected error containing " + substr + ", no exception thrown"); 148 }; 149 } 150 151 if (typeof globalThis.assertThrowsInstanceOfWithMessageCheck === 'undefined') { 152 globalThis.assertThrowsInstanceOfWithMessageCheck = function assertThrowsInstanceOfWithMessageCheck(f, ctor, check, msg) { 153 var fullmsg; 154 try { 155 f(); 156 } catch (exc) { 157 if (!(exc instanceof ctor)) 158 fullmsg = `Assertion failed: expected exception ${ctor.name}, got ${exc}`; 159 else { 160 var result = check(exc.message); 161 if (!result) 162 fullmsg = `Assertion failed: expected exception with message, got ${exc}`; 163 else 164 return; 165 } 166 } 167 168 if (fullmsg === undefined) 169 fullmsg = `Assertion failed: expected exception ${ctor.name}, no exception thrown`; 170 if (msg !== undefined) 171 fullmsg += " - " + msg; 172 173 throw new Error(fullmsg); 174 }; 175 } 176 177 if (typeof globalThis.assertThrowsInstanceOf === 'undefined') { 178 globalThis.assertThrowsInstanceOf = function assertThrowsInstanceOf(f, ctor, msg) { 179 assertThrowsInstanceOfWithMessageCheck(f, ctor, _ => true, msg); 180 }; 181 } 182 183 if (typeof globalThis.assertThrowsInstanceOfWithMessage === 'undefined') { 184 globalThis.assertThrowsInstanceOfWithMessage = function assertThrowsInstanceOfWithMessage(f, ctor, expected, msg) { 185 assertThrowsInstanceOfWithMessageCheck(f, ctor, message => message === expected, msg); 186 } 187 } 188 189 if (typeof globalThis.assertThrowsInstanceOfWithMessageContains === 'undefined') { 190 globalThis.assertThrowsInstanceOfWithMessageContains = function assertThrowsInstanceOfWithMessageContains(f, ctor, substr, msg) { 191 assertThrowsInstanceOfWithMessageCheck(f, ctor, message => message.indexOf(substr) !== -1, msg); 192 } 193 } 194 195 globalThis.assertDeepEq = (function(){ 196 var call = Function.prototype.call, 197 Array_isArray = Array.isArray, 198 Array_includes = call.bind(Array.prototype.includes), 199 Map_ = Map, 200 Error_ = Error, 201 Symbol_ = Symbol, 202 Symbol_keyFor = Symbol.keyFor, 203 Symbol_description = call.bind(Object.getOwnPropertyDescriptor(Symbol.prototype, "description").get), 204 Map_has = call.bind(Map.prototype.has), 205 Map_get = call.bind(Map.prototype.get), 206 Map_set = call.bind(Map.prototype.set), 207 Object_toString = call.bind(Object.prototype.toString), 208 Function_toString = call.bind(Function.prototype.toString), 209 Object_getPrototypeOf = Object.getPrototypeOf, 210 Object_hasOwnProperty = call.bind(Object.prototype.hasOwnProperty), 211 Object_getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, 212 Object_isExtensible = Object.isExtensible, 213 Object_getOwnPropertyNames = Object.getOwnPropertyNames; 214 215 // Return true iff ES6 Type(v) isn't Object. 216 // Note that `typeof document.all === "undefined"`. 217 function isPrimitive(v) { 218 return (v === null || 219 v === undefined || 220 typeof v === "boolean" || 221 typeof v === "number" || 222 typeof v === "string" || 223 typeof v === "symbol"); 224 } 225 226 function assertSameValue(a, b, msg) { 227 try { 228 assertEq(a, b); 229 } catch (exc) { 230 throw Error_(exc.message + (msg ? " " + msg : "")); 231 } 232 } 233 234 function assertSameClass(a, b, msg) { 235 var ac = Object_toString(a), bc = Object_toString(b); 236 assertSameValue(ac, bc, msg); 237 switch (ac) { 238 case "[object Function]": 239 if (typeof isProxy !== "undefined" && !isProxy(a) && !isProxy(b)) 240 assertSameValue(Function_toString(a), Function_toString(b), msg); 241 } 242 } 243 244 function at(prevmsg, segment) { 245 return prevmsg ? prevmsg + segment : "at _" + segment; 246 } 247 248 // Assert that the arguments a and b are thoroughly structurally equivalent. 249 // 250 // For the sake of speed, we cut a corner: 251 // var x = {}, y = {}, ax = [x]; 252 // assertDeepEq([ax, x], [ax, y]); // passes (?!) 253 // 254 // Technically this should fail, since the two object graphs are different. 255 // (The graph of [ax, y] contains one more object than the graph of [ax, x].) 256 // 257 // To get technically correct behavior, pass {strictEquivalence: true}. 258 // This is slower because we have to walk the entire graph, and Object.prototype 259 // is big. 260 // 261 return function assertDeepEq(a, b, options) { 262 var strictEquivalence = options ? options.strictEquivalence : false; 263 264 function assertSameProto(a, b, msg) { 265 check(Object_getPrototypeOf(a), Object_getPrototypeOf(b), at(msg, ".__proto__")); 266 } 267 268 function failPropList(na, nb, msg) { 269 throw Error_("got own properties " + JSON.stringify(na) + ", expected " + JSON.stringify(nb) + 270 (msg ? " " + msg : "")); 271 } 272 273 function assertSameProps(a, b, msg) { 274 var na = Object_getOwnPropertyNames(a), 275 nb = Object_getOwnPropertyNames(b); 276 if (na.length !== nb.length) 277 failPropList(na, nb, msg); 278 279 // Ignore differences in whether Array elements are stored densely. 280 if (Array_isArray(a)) { 281 na.sort(); 282 nb.sort(); 283 } 284 285 for (var i = 0; i < na.length; i++) { 286 var name = na[i]; 287 if (name !== nb[i]) 288 failPropList(na, nb, msg); 289 var da = Object_getOwnPropertyDescriptor(a, name), 290 db = Object_getOwnPropertyDescriptor(b, name); 291 var pmsg = at(msg, /^[_$A-Za-z0-9]+$/.test(name) 292 ? /0|[1-9][0-9]*/.test(name) ? "[" + name + "]" : "." + name 293 : "[" + JSON.stringify(name) + "]"); 294 assertSameValue(da.configurable, db.configurable, at(pmsg, ".[[Configurable]]")); 295 assertSameValue(da.enumerable, db.enumerable, at(pmsg, ".[[Enumerable]]")); 296 if (Object_hasOwnProperty(da, "value")) { 297 if (!Object_hasOwnProperty(db, "value")) 298 throw Error_("got data property, expected accessor property" + pmsg); 299 check(da.value, db.value, pmsg); 300 } else { 301 if (Object_hasOwnProperty(db, "value")) 302 throw Error_("got accessor property, expected data property" + pmsg); 303 check(da.get, db.get, at(pmsg, ".[[Get]]")); 304 check(da.set, db.set, at(pmsg, ".[[Set]]")); 305 } 306 } 307 }; 308 309 const wellKnownSymbols = Reflect.ownKeys(Symbol) 310 .map(key => Symbol[key]) 311 .filter(value => typeof value === "symbol"); 312 313 // The standard doesn't offer a convenient way to distinguish well-known 314 // symbols from user-created symbols. 315 function isSimilarSymbol(a, b) { 316 // Fast path for same symbols. 317 if (a === b) { 318 return true; 319 } 320 321 // 1. Symbol descriptions must match. 322 // 2. Either both symbols are in the registry or none is. 323 // 3. Neither symbol must be a well-known symbol, because those are 324 // already handled through the fast path. 325 return Symbol_description(a) === Symbol_description(b) && 326 Symbol_keyFor(a) === Symbol_keyFor(b) && 327 !Array_includes(wellKnownSymbols, a) && 328 !Array_includes(wellKnownSymbols, b); 329 } 330 331 var ab = new Map_(); 332 var bpath = new Map_(); 333 334 function check(a, b, path) { 335 if (typeof a === "symbol") { 336 // Symbols are primitives, but they have identity. 337 // Symbol("x") !== Symbol("x") but 338 // assertDeepEq(Symbol("x"), Symbol("x")) should pass. 339 if (typeof b !== "symbol") { 340 throw Error_("got " + String(a) + ", expected " + String(b) + " " + path); 341 } else if (!isSimilarSymbol(a, b)) { 342 throw Error_("got " + String(a) + ", expected " + String(b) + " " + path); 343 } else if (Map_has(ab, a)) { 344 assertSameValue(Map_get(ab, a), b, path); 345 } else if (Map_has(bpath, b)) { 346 var bPrevPath = Map_get(bpath, b) || "_"; 347 throw Error_("got distinct symbols " + at(path, "") + " and " + 348 at(bPrevPath, "") + ", expected the same symbol both places"); 349 } else { 350 Map_set(ab, a, b); 351 Map_set(bpath, b, path); 352 } 353 } else if (isPrimitive(a)) { 354 assertSameValue(a, b, path); 355 } else if (isPrimitive(b)) { 356 throw Error_("got " + Object_toString(a) + ", expected " + String(b) + " " + path); 357 } else if (Map_has(ab, a)) { 358 assertSameValue(Map_get(ab, a), b, path); 359 } else if (Map_has(bpath, b)) { 360 var bPrevPath = Map_get(bpath, b) || "_"; 361 throw Error_("got distinct objects " + at(path, "") + " and " + at(bPrevPath, "") + 362 ", expected the same object both places"); 363 } else { 364 Map_set(ab, a, b); 365 Map_set(bpath, b, path); 366 if (a !== b || strictEquivalence) { 367 assertSameClass(a, b, path); 368 assertSameProto(a, b, path); 369 assertSameProps(a, b, path); 370 assertSameValue(Object_isExtensible(a), 371 Object_isExtensible(b), 372 at(path, ".[[Extensible]]")); 373 } 374 } 375 } 376 377 check(a, b, ""); 378 }; 379 })(); 380 381 })();