shell.js (18295B)
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 // NOTE: If you're adding new test harness functionality -- first, should you 7 // at all? Most stuff is better in specific tests, or in nested shell.js 8 // or browser.js. Second, supposing you should, please add it to this 9 // IIFE for better modularity/resilience against tests that must do 10 // particularly bizarre things that might break the harness. 11 12 (function(global) { 13 "use strict"; 14 15 /********************************************************************** 16 * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) * 17 **********************************************************************/ 18 19 var undefined; // sigh 20 21 var Error = global.Error; 22 var Function = global.Function; 23 var Number = global.Number; 24 var RegExp = global.RegExp; 25 var String = global.String; 26 var Symbol = global.Symbol; 27 var TypeError = global.TypeError; 28 29 var ArrayIsArray = global.Array.isArray; 30 var MathAbs = global.Math.abs; 31 var ObjectCreate = global.Object.create; 32 var ObjectDefineProperty = global.Object.defineProperty; 33 var ReflectApply = global.Reflect.apply; 34 var RegExpPrototypeExec = global.RegExp.prototype.exec; 35 var StringPrototypeCharCodeAt = global.String.prototype.charCodeAt; 36 var StringPrototypeIndexOf = global.String.prototype.indexOf; 37 var StringPrototypeSubstring = global.String.prototype.substring; 38 39 var runningInBrowser = typeof global.window !== "undefined"; 40 if (runningInBrowser) { 41 // Certain cached functionality only exists (and is only needed) when 42 // running in the browser. Segregate that caching here. 43 44 var SpecialPowersSetGCZeal = 45 global.SpecialPowers ? global.SpecialPowers.setGCZeal : undefined; 46 } 47 48 var evaluate = global.evaluate; 49 var options = global.options; 50 51 /**************************** 52 * GENERAL HELPER FUNCTIONS * 53 ****************************/ 54 55 // We *cannot* use Array.prototype.push for this, because that function sets 56 // the new trailing element, which could invoke a setter (left by a test) on 57 // Array.prototype or Object.prototype. 58 function ArrayPush(arr, val) { 59 assertEq(ArrayIsArray(arr), true, 60 "ArrayPush must only be used on actual arrays"); 61 62 var desc = ObjectCreate(null); 63 desc.value = val; 64 desc.enumerable = true; 65 desc.configurable = true; 66 desc.writable = true; 67 ObjectDefineProperty(arr, arr.length, desc); 68 } 69 70 function StringCharCodeAt(str, index) { 71 return ReflectApply(StringPrototypeCharCodeAt, str, [index]); 72 } 73 74 function StringSplit(str, delimiter) { 75 assertEq(typeof str === "string" && typeof delimiter === "string", true, 76 "StringSplit must be called with two string arguments"); 77 assertEq(delimiter.length > 0, true, 78 "StringSplit doesn't support an empty delimiter string"); 79 80 var parts = []; 81 var last = 0; 82 while (true) { 83 var i = ReflectApply(StringPrototypeIndexOf, str, [delimiter, last]); 84 if (i < 0) { 85 if (last < str.length) 86 ArrayPush(parts, ReflectApply(StringPrototypeSubstring, str, [last])); 87 return parts; 88 } 89 90 ArrayPush(parts, ReflectApply(StringPrototypeSubstring, str, [last, i])); 91 last = i + delimiter.length; 92 } 93 } 94 95 function shellOptionsClear() { 96 assertEq(runningInBrowser, false, "Only called when running in the shell."); 97 98 // Return early if no options are set. 99 var currentOptions = options ? options() : ""; 100 if (currentOptions === "") 101 return; 102 103 // Turn off current settings. 104 var optionNames = StringSplit(currentOptions, ","); 105 for (var i = 0; i < optionNames.length; i++) { 106 options(optionNames[i]); 107 } 108 } 109 110 /**************************** 111 * TESTING FUNCTION EXPORTS * 112 ****************************/ 113 114 function SameValue(v1, v2) { 115 // We could |return Object.is(v1, v2);|, but that's less portable. 116 if (v1 === 0 && v2 === 0) 117 return 1 / v1 === 1 / v2; 118 if (v1 !== v1 && v2 !== v2) 119 return true; 120 return v1 === v2; 121 } 122 123 var assertEq = global.assertEq; 124 if (typeof assertEq !== "function") { 125 assertEq = function assertEq(actual, expected, message) { 126 if (!SameValue(actual, expected)) { 127 throw new TypeError(`Assertion failed: got "${actual}", expected "${expected}"` + 128 (message ? ": " + message : "")); 129 } 130 }; 131 global.assertEq = assertEq; 132 } 133 134 function assertEqArray(actual, expected) { 135 var len = actual.length; 136 assertEq(len, expected.length, "mismatching array lengths"); 137 138 var i = 0; 139 try { 140 for (; i < len; i++) 141 assertEq(actual[i], expected[i], "mismatch at element " + i); 142 } catch (e) { 143 throw new Error(`Exception thrown at index ${i}: ${e}`); 144 } 145 } 146 global.assertEqArray = assertEqArray; 147 148 function assertThrows(f) { 149 if (arguments.length != 1) { 150 throw new Error("Too many arguments to assertThrows; maybe you meant assertThrowsInstanceOf?"); 151 } 152 var ok = false; 153 try { 154 f(); 155 } catch (exc) { 156 ok = true; 157 } 158 if (!ok) 159 throw new Error(`Assertion failed: ${f} did not throw as expected`); 160 } 161 global.assertThrows = assertThrows; 162 163 function assertThrowsInstanceOf(f, ctor, msg) { 164 var fullmsg; 165 try { 166 f(); 167 } catch (exc) { 168 if (exc instanceof ctor) 169 return; 170 fullmsg = `Assertion failed: expected exception ${ctor.name}, got ${exc}`; 171 } 172 173 if (fullmsg === undefined) 174 fullmsg = `Assertion failed: expected exception ${ctor.name}, no exception thrown`; 175 if (msg !== undefined) 176 fullmsg += " - " + msg; 177 178 throw new Error(fullmsg); 179 } 180 global.assertThrowsInstanceOf = assertThrowsInstanceOf; 181 182 /**************************** 183 * UTILITY FUNCTION EXPORTS * 184 ****************************/ 185 186 var dump = global.dump; 187 if (typeof global.dump === "function") { 188 // A presumptively-functional |dump| exists, so no need to do anything. 189 } else { 190 // We don't have |dump|. Try to simulate the desired effect another way. 191 if (runningInBrowser) { 192 // We can't actually print to the console: |global.print| invokes browser 193 // printing functionality here (it's overwritten just below), and 194 // |global.dump| isn't a function that'll dump to the console (presumably 195 // because the preference to enable |dump| wasn't set). Just make it a 196 // no-op. 197 dump = function() {}; 198 } else { 199 // |print| prints to stdout: make |dump| do likewise. 200 dump = global.print; 201 } 202 global.dump = dump; 203 } 204 205 var print; 206 if (runningInBrowser) { 207 // We're executing in a browser. Using |global.print| would invoke browser 208 // printing functionality: not what tests want! Instead, use a print 209 // function that syncs up with the test harness and console. 210 print = function print() { 211 var s = "TEST-INFO | "; 212 for (var i = 0; i < arguments.length; i++) 213 s += String(arguments[i]) + " "; 214 215 // Dump the string to the console for developers and the harness. 216 dump(s + "\n"); 217 218 // AddPrintOutput doesn't require HTML special characters be escaped. 219 global.AddPrintOutput(s); 220 }; 221 222 global.print = print; 223 } else { 224 // We're executing in a shell, and |global.print| is the desired function. 225 print = global.print; 226 } 227 228 var gczeal = global.gczeal; 229 if (typeof gczeal !== "function") { 230 if (typeof SpecialPowersSetGCZeal === "function") { 231 gczeal = function gczeal(z) { 232 SpecialPowersSetGCZeal(z); 233 }; 234 } else { 235 gczeal = function() {}; // no-op if not available 236 } 237 238 global.gczeal = gczeal; 239 } 240 241 // Evaluates the given source code as global script code. browser.js provides 242 // a different implementation for this function. 243 var evaluateScript = global.evaluateScript; 244 if (typeof evaluate === "function" && typeof evaluateScript !== "function") { 245 evaluateScript = function evaluateScript(code) { 246 evaluate(String(code)); 247 }; 248 249 global.evaluateScript = evaluateScript; 250 } 251 252 function toPrinted(value) { 253 value = String(value); 254 255 var digits = "0123456789ABCDEF"; 256 var result = ""; 257 for (var i = 0; i < value.length; i++) { 258 var ch = StringCharCodeAt(value, i); 259 if (ch === 0x5C && i + 1 < value.length) { 260 var d = value[i + 1]; 261 if (d === "n") { 262 result += "NL"; 263 i++; 264 } else if (d === "r") { 265 result += "CR"; 266 i++; 267 } else { 268 result += "\\"; 269 } 270 } else if (ch === 0x0A) { 271 result += "NL"; 272 } else if (ch < 0x20 || ch > 0x7E) { 273 var a = digits[ch & 0xf]; 274 ch >>= 4; 275 var b = digits[ch & 0xf]; 276 ch >>= 4; 277 278 if (ch) { 279 var c = digits[ch & 0xf]; 280 ch >>= 4; 281 var d = digits[ch & 0xf]; 282 283 result += "\\u" + d + c + b + a; 284 } else { 285 result += "\\x" + b + a; 286 } 287 } else { 288 result += value[i]; 289 } 290 } 291 292 return result; 293 } 294 295 /* 296 * An xorshift pseudo-random number generator see: 297 * https://en.wikipedia.org/wiki/Xorshift#xorshift.2A 298 * This generator will always produce a value, n, where 299 * 0 <= n <= 255 300 */ 301 function *XorShiftGenerator(seed, size) { 302 let x = seed; 303 for (let i = 0; i < size; i++) { 304 x ^= x >> 12; 305 x ^= x << 25; 306 x ^= x >> 27; 307 yield x % 256; 308 } 309 } 310 global.XorShiftGenerator = XorShiftGenerator; 311 312 /************************************************************************* 313 * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) * 314 *************************************************************************/ 315 316 var PASSED = " PASSED! "; 317 var FAILED = " FAILED! "; 318 319 /* 320 * Same as `new TestCase(description, expect, actual)`, except it doesn't 321 * return the newly created test case object. 322 */ 323 function AddTestCase(description, expect, actual) { 324 new TestCase(description, expect, actual); 325 } 326 global.AddTestCase = AddTestCase; 327 328 var testCasesArray = []; 329 330 function TestCase(d, e, a, r) { 331 this.description = d; 332 this.expect = e; 333 this.actual = a; 334 this.passed = getTestCaseResult(e, a); 335 this.reason = typeof r !== 'undefined' ? String(r) : ''; 336 337 ArrayPush(testCasesArray, this); 338 } 339 global.TestCase = TestCase; 340 341 TestCase.prototype = ObjectCreate(null); 342 TestCase.prototype.testPassed = (function TestCase_testPassed() { return this.passed; }); 343 TestCase.prototype.testFailed = (function TestCase_testFailed() { return !this.passed; }); 344 TestCase.prototype.testDescription = (function TestCase_testDescription() { return this.description + ' ' + this.reason; }); 345 346 function getTestCaseResult(expected, actual) { 347 if (typeof expected !== typeof actual) 348 return false; 349 if (typeof expected !== 'number') 350 // Note that many tests depend on the use of '==' here, not '==='. 351 return actual == expected; 352 353 // Distinguish NaN from other values. Using x !== x comparisons here 354 // works even if tests redefine isNaN. 355 if (actual !== actual) 356 return expected !== expected; 357 if (expected !== expected) 358 return false; 359 360 // Tolerate a certain degree of error. 361 if (actual !== expected) 362 return MathAbs(actual - expected) <= 1E-10; 363 364 // Here would be a good place to distinguish 0 and -0, if we wanted 365 // to. However, doing so would introduce a number of failures in 366 // areas where they don't seem important. For example, the WeekDay 367 // function in ECMA-262 returns -0 for Sundays before the epoch, but 368 // the Date functions in SpiderMonkey specified in terms of WeekDay 369 // often don't. This seems unimportant. 370 return true; 371 } 372 373 function reportTestCaseResult(description, expected, actual, output) { 374 var testcase = new TestCase(description, expected, actual, output); 375 376 // if running under reftest, let it handle result reporting. 377 if (!runningInBrowser) { 378 if (testcase.passed) { 379 print(PASSED + description); 380 } else { 381 reportFailure(description + " : " + output); 382 } 383 } 384 } 385 386 function getTestCases() { 387 return testCasesArray; 388 } 389 global.getTestCases = getTestCases; 390 391 /* 392 * The test driver searches for such a phrase in the test output. 393 * If such phrase exists, it will set n as the expected exit code. 394 */ 395 function expectExitCode(n) { 396 print('--- NOTE: IN THIS TESTCASE, WE EXPECT EXIT CODE ' + n + ' ---'); 397 } 398 global.expectExitCode = expectExitCode; 399 400 /* 401 * Statuses current section of a test 402 */ 403 function inSection(x) { 404 return "Section " + x + " of test - "; 405 } 406 global.inSection = inSection; 407 408 /* 409 * Report a failure in the 'accepted' manner 410 */ 411 function reportFailure(msg) { 412 msg = String(msg); 413 var lines = StringSplit(msg, "\n"); 414 415 for (var i = 0; i < lines.length; i++) 416 print(FAILED + " " + lines[i]); 417 } 418 global.reportFailure = reportFailure; 419 420 /* 421 * Print a non-failure message. 422 */ 423 function printStatus(msg) { 424 msg = String(msg); 425 var lines = StringSplit(msg, "\n"); 426 427 for (var i = 0; i < lines.length; i++) 428 print("STATUS: " + lines[i]); 429 } 430 global.printStatus = printStatus; 431 432 /* 433 * Print a bugnumber message. 434 */ 435 function printBugNumber(num) { 436 print('BUGNUMBER: ' + num); 437 } 438 global.printBugNumber = printBugNumber; 439 440 /* 441 * Compare expected result to actual result, if they differ (in value and/or 442 * type) report a failure. If description is provided, include it in the 443 * failure report. 444 */ 445 function reportCompare(expected, actual, description) { 446 var expected_t = typeof expected; 447 var actual_t = typeof actual; 448 var output = ""; 449 450 if (typeof description === "undefined") 451 description = ""; 452 453 if (expected_t !== actual_t) 454 output += `Type mismatch, expected type ${expected_t}, actual type ${actual_t} `; 455 456 if (expected != actual) 457 output += `Expected value '${toPrinted(expected)}', Actual value '${toPrinted(actual)}' `; 458 459 reportTestCaseResult(description, expected, actual, output); 460 } 461 global.reportCompare = reportCompare; 462 463 /* 464 * Attempt to match a regular expression describing the result to 465 * the actual result, if they differ (in value and/or 466 * type) report a failure. If description is provided, include it in the 467 * failure report. 468 */ 469 function reportMatch(expectedRegExp, actual, description) { 470 var expected_t = "string"; 471 var actual_t = typeof actual; 472 var output = ""; 473 474 if (typeof description === "undefined") 475 description = ""; 476 477 if (expected_t !== actual_t) 478 output += `Type mismatch, expected type ${expected_t}, actual type ${actual_t} `; 479 480 var matches = ReflectApply(RegExpPrototypeExec, expectedRegExp, [actual]) !== null; 481 if (!matches) { 482 output += 483 `Expected match to '${toPrinted(expectedRegExp)}', Actual value '${toPrinted(actual)}' `; 484 } 485 486 reportTestCaseResult(description, true, matches, output); 487 } 488 global.reportMatch = reportMatch; 489 490 function compareSource(expect, actual, summary) { 491 // compare source 492 var expectP = String(expect); 493 var actualP = String(actual); 494 495 print('expect:\n' + expectP); 496 print('actual:\n' + actualP); 497 498 reportCompare(expectP, actualP, summary); 499 500 // actual must be compilable if expect is? 501 try { 502 var expectCompile = 'No Error'; 503 var actualCompile; 504 505 Function(expect); 506 try { 507 Function(actual); 508 actualCompile = 'No Error'; 509 } catch(ex1) { 510 actualCompile = ex1 + ''; 511 } 512 reportCompare(expectCompile, actualCompile, 513 summary + ': compile actual'); 514 } catch(ex) { 515 } 516 } 517 global.compareSource = compareSource; 518 519 function test() { 520 var testCases = getTestCases(); 521 for (var i = 0; i < testCases.length; i++) { 522 var testCase = testCases[i]; 523 testCase.reason += testCase.passed ? "" : "wrong value "; 524 525 // if running under reftest, let it handle result reporting. 526 if (!runningInBrowser) { 527 var message = `${testCase.description} = ${testCase.actual} expected: ${testCase.expect}`; 528 print((testCase.passed ? PASSED : FAILED) + message); 529 } 530 } 531 } 532 global.test = test; 533 534 // This function uses the shell's print function. When running tests in the 535 // browser, browser.js overrides this function to write to the page. 536 function writeHeaderToLog(string) { 537 print(string); 538 } 539 global.writeHeaderToLog = writeHeaderToLog; 540 541 /************************************ 542 * PROMISE TESTING FUNCTION EXPORTS * 543 ************************************/ 544 545 function getPromiseResult(promise) { 546 var result, error, caught = false; 547 promise.then(r => { result = r; }, 548 e => { caught = true; error = e; }); 549 drainJobQueue(); 550 if (caught) 551 throw error; 552 return result; 553 } 554 global.getPromiseResult = getPromiseResult; 555 556 function assertEventuallyEq(promise, expected) { 557 assertEq(getPromiseResult(promise), expected); 558 } 559 global.assertEventuallyEq = assertEventuallyEq; 560 561 function assertEventuallyThrows(promise, expectedErrorType) { 562 assertThrowsInstanceOf(() => getPromiseResult(promise), expectedErrorType); 563 }; 564 global.assertEventuallyThrows = assertEventuallyThrows; 565 566 function assertEventuallyDeepEq(promise, expected) { 567 assertDeepEq(getPromiseResult(promise), expected); 568 }; 569 global.assertEventuallyDeepEq = assertEventuallyDeepEq; 570 571 /******************************************* 572 * RUN ONCE CODE TO SETUP ADDITIONAL STATE * 573 *******************************************/ 574 575 // Clear all options before running any tests. browser.js performs this 576 // set-up as part of its jsTestDriverBrowserInit function. 577 if (!runningInBrowser) { 578 shellOptionsClear(); 579 } 580 581 if (!runningInBrowser) { 582 // Set the minimum heap size for parallel marking to zero for testing 583 // purposes. We don't have access to gcparam in the browser. 584 gcparam('parallelMarkingThresholdMB', 0); 585 } 586 })(this); 587 588 var DESCRIPTION;