TestRunner.js (35642B)
1 /* -*- js-indent-level: 4; indent-tabs-mode: nil -*- */ 2 /* 3 * e10s event dispatcher from content->chrome 4 * 5 * type = eventName (QuitApplication) 6 * data = json object {"filename":filename} <- for LoggerInit 7 */ 8 9 // This file expects the following files to be loaded. 10 /* import-globals-from LogController.js */ 11 /* import-globals-from MemoryStats.js */ 12 /* import-globals-from MozillaLogger.js */ 13 14 /* eslint-disable no-unsanitized/property */ 15 16 "use strict"; 17 18 const { StructuredLogger, StructuredFormatter } = 19 SpecialPowers.ChromeUtils.importESModule( 20 "resource://testing-common/StructuredLog.sys.mjs" 21 ); 22 23 function getElement(id) { 24 return typeof id == "string" ? document.getElementById(id) : id; 25 } 26 27 this.$ = this.getElement; 28 29 function contentDispatchEvent(type, data, sync) { 30 if (typeof data == "undefined") { 31 data = {}; 32 } 33 34 var event = new CustomEvent("contentEvent", { 35 bubbles: true, 36 detail: { 37 sync, 38 type, 39 data: JSON.stringify(data), 40 }, 41 }); 42 document.dispatchEvent(event); 43 } 44 45 function contentAsyncEvent(type, data) { 46 contentDispatchEvent(type, data, 0); 47 } 48 49 /* Helper Function */ 50 function extend(obj, /* optional */ skip) { 51 // Extend an array with an array-like object starting 52 // from the skip index 53 if (!skip) { 54 skip = 0; 55 } 56 if (obj) { 57 var l = obj.length; 58 var ret = []; 59 for (var i = skip; i < l; i++) { 60 ret.push(obj[i]); 61 } 62 } 63 return ret; 64 } 65 66 function flattenArguments(/* ...*/) { 67 var res = []; 68 var args = extend(arguments); 69 while (args.length) { 70 var o = args.shift(); 71 if (o && typeof o == "object" && typeof o.length == "number") { 72 for (var i = o.length - 1; i >= 0; i--) { 73 args.unshift(o[i]); 74 } 75 } else { 76 res.push(o); 77 } 78 } 79 return res; 80 } 81 82 function testInXOriginFrame() { 83 // Check if the test running in an iframe is a cross origin test. 84 try { 85 $("testframe").contentWindow.origin; 86 return false; 87 } catch (e) { 88 return true; 89 } 90 } 91 92 function testInDifferentProcess() { 93 // Check if the test running in an iframe that is loaded in a different process. 94 return SpecialPowers.Cu.isRemoteProxy($("testframe").contentWindow); 95 } 96 97 /** 98 * TestRunner: A test runner for SimpleTest 99 * TODO: 100 * 101 * * Avoid moving iframes: That causes reloads on mozilla and opera. 102 * 103 * 104 */ 105 var TestRunner = {}; 106 TestRunner.logEnabled = false; 107 TestRunner._currentTest = 0; 108 TestRunner._lastTestFinished = -1; 109 TestRunner._loopIsRestarting = false; 110 TestRunner.currentTestURL = ""; 111 TestRunner.originalTestURL = ""; 112 TestRunner._urls = []; 113 TestRunner._lastAssertionCount = 0; 114 TestRunner._expectedMinAsserts = 0; 115 TestRunner._expectedMaxAsserts = 0; 116 117 TestRunner.timeout = 300 * 1000; // 5 minutes. 118 TestRunner.maxTimeouts = 4; // halt testing after too many timeouts 119 TestRunner.runSlower = false; 120 TestRunner.dumpOutputDirectory = ""; 121 TestRunner.dumpAboutMemoryAfterTest = false; 122 TestRunner.dumpDMDAfterTest = false; 123 TestRunner.slowestTestTime = 0; 124 TestRunner.slowestTestURL = ""; 125 TestRunner.interactiveDebugger = false; 126 TestRunner.cleanupCrashes = false; 127 TestRunner.timeoutAsPass = false; 128 TestRunner.conditionedProfile = false; 129 TestRunner.comparePrefs = false; 130 131 TestRunner._expectingProcessCrash = false; 132 TestRunner._structuredFormatter = new StructuredFormatter(); 133 134 /** 135 * Make sure the tests don't hang indefinitely. 136 */ 137 TestRunner._numTimeouts = 0; 138 TestRunner._currentTestStartTime = new Date().valueOf(); 139 TestRunner._timeoutFactor = 1; 140 141 /** 142 * Used to collect code coverage with the js debugger. 143 */ 144 TestRunner.jscovDirPrefix = ""; 145 var coverageCollector = {}; 146 147 function record(succeeded, expectedFail, msg) { 148 let successInfo; 149 let failureInfo; 150 if (expectedFail) { 151 successInfo = { 152 status: "PASS", 153 expected: "FAIL", 154 message: "TEST-UNEXPECTED-PASS", 155 }; 156 failureInfo = { 157 status: "FAIL", 158 expected: "FAIL", 159 message: "TEST-KNOWN-FAIL", 160 }; 161 } else { 162 successInfo = { 163 status: "PASS", 164 expected: "PASS", 165 message: "TEST-PASS", 166 }; 167 failureInfo = { 168 status: "FAIL", 169 expected: "PASS", 170 message: "TEST-UNEXPECTED-FAIL", 171 }; 172 } 173 174 let result = succeeded ? successInfo : failureInfo; 175 176 TestRunner.structuredLogger.testStatus( 177 TestRunner.currentTestURL, 178 msg, 179 result.status, 180 result.expected, 181 "", 182 "" 183 ); 184 } 185 186 TestRunner._checkForHangs = function () { 187 function reportError(win, msg) { 188 if (testInXOriginFrame() || "SimpleTest" in win) { 189 record(false, TestRunner.timeoutAsPass, msg); 190 } else if ("W3CTest" in win) { 191 win.W3CTest.logFailure(msg); 192 } 193 } 194 195 async function killTest(win) { 196 if (testInXOriginFrame()) { 197 win.postMessage("SimpleTest:timeout", "*"); 198 } else if ("SimpleTest" in win) { 199 await win.SimpleTest.timeout(); 200 win.SimpleTest.finish(); 201 } else if ("W3CTest" in win) { 202 await win.W3CTest.timeout(); 203 } 204 } 205 206 if (TestRunner._currentTest < TestRunner._urls.length) { 207 var runtime = new Date().valueOf() - TestRunner._currentTestStartTime; 208 if ( 209 !TestRunner.interactiveDebugger && 210 runtime >= TestRunner.timeout * TestRunner._timeoutFactor 211 ) { 212 let testIframe = $("testframe"); 213 var frameWindow = 214 (!testInXOriginFrame() && testIframe.contentWindow.wrappedJSObject) || 215 testIframe.contentWindow; 216 reportError(frameWindow, "Test timed out."); 217 TestRunner.updateUI([{ result: false }]); 218 219 // If we have too many timeouts, give up. We don't want to wait hours 220 // for results if some bug causes lots of tests to time out. 221 if ( 222 ++TestRunner._numTimeouts >= TestRunner.maxTimeouts || 223 TestRunner.runUntilFailure 224 ) { 225 TestRunner._haltTests = true; 226 227 TestRunner.currentTestURL = "(SimpleTest/TestRunner.js)"; 228 reportError( 229 frameWindow, 230 TestRunner.maxTimeouts + " test timeouts, giving up." 231 ); 232 var skippedTests = TestRunner._urls.length - TestRunner._currentTest; 233 reportError( 234 frameWindow, 235 "Skipping " + skippedTests + " remaining tests." 236 ); 237 } 238 239 // Add a little (1 second) delay to ensure automation.py has time to notice 240 // "Test timed out" log and process it (= take a screenshot). 241 setTimeout(async function delayedKillTest() { 242 try { 243 await killTest(frameWindow); 244 } catch (e) { 245 reportError(frameWindow, "Test error: " + e); 246 TestRunner.updateUI([{ result: false }]); 247 } 248 }, 1000); 249 250 if (TestRunner._haltTests) { 251 return; 252 } 253 } 254 255 setTimeout(TestRunner._checkForHangs, 30000); 256 } 257 }; 258 259 TestRunner.requestLongerTimeout = function (factor) { 260 TestRunner._timeoutFactor = factor; 261 }; 262 263 /** 264 * This is used to loop tests 265 */ 266 TestRunner.repeat = 0; 267 TestRunner._currentLoop = 1; 268 269 TestRunner.expectAssertions = function (min, max) { 270 if (typeof max == "undefined") { 271 max = min; 272 } 273 if ( 274 typeof min != "number" || 275 typeof max != "number" || 276 min < 0 || 277 max < min 278 ) { 279 throw new Error("bad parameter to expectAssertions"); 280 } 281 TestRunner._expectedMinAsserts = min; 282 TestRunner._expectedMaxAsserts = max; 283 }; 284 285 /** 286 * This function is called after generating the summary. 287 */ 288 TestRunner.onComplete = null; 289 290 /** 291 * Adds a failed test case to a list so we can rerun only the failed tests 292 */ 293 TestRunner._failedTests = {}; 294 TestRunner._failureFile = ""; 295 296 TestRunner.addFailedTest = function (testName) { 297 if (TestRunner._failedTests[testName] == undefined) { 298 TestRunner._failedTests[testName] = ""; 299 } 300 }; 301 302 TestRunner.setFailureFile = function (fileName) { 303 TestRunner._failureFile = fileName; 304 }; 305 306 TestRunner.generateFailureList = function () { 307 if (TestRunner._failureFile) { 308 var failures = new MozillaFileLogger(TestRunner._failureFile); 309 failures.log(JSON.stringify(TestRunner._failedTests)); 310 failures.close(); 311 } 312 }; 313 314 /** 315 * If logEnabled is true, this is the logger that will be used. 316 */ 317 318 // This delimiter is used to avoid interleaving Mochitest/Gecko logs. 319 var LOG_DELIMITER = "\ue175\uee31\u2c32\uacbf"; 320 321 // A log callback for StructuredLog.sys.mjs 322 TestRunner._dumpMessage = function (message) { 323 var str; 324 325 // This is a directive to python to format these messages 326 // for compatibility with mozharness. This can be removed 327 // with the MochitestFormatter (see bug 1045525). 328 message.js_source = "TestRunner.js"; 329 if ( 330 TestRunner.interactiveDebugger && 331 message.action in TestRunner._structuredFormatter 332 ) { 333 str = TestRunner._structuredFormatter[message.action](message); 334 } else { 335 str = LOG_DELIMITER + JSON.stringify(message) + LOG_DELIMITER; 336 } 337 // BUGFIX: browser-chrome tests don't use LogController 338 if (Object.keys(LogController.listeners).length !== 0) { 339 LogController.log(str); 340 } else { 341 dump("\n" + str + "\n"); 342 } 343 // Checking for error messages 344 if (message.expected || message.level === "ERROR") { 345 TestRunner.failureHandler(); 346 } 347 }; 348 349 // From https://searchfox.org/mozilla-central/source/testing/modules/StructuredLog.sys.mjs 350 TestRunner.structuredLogger = new StructuredLogger( 351 "mochitest", 352 TestRunner._dumpMessage, 353 [], 354 TestRunner 355 ); 356 TestRunner.structuredLogger.deactivateBuffering = function () { 357 TestRunner.structuredLogger.logData("buffering_off"); 358 }; 359 TestRunner.structuredLogger.activateBuffering = function () { 360 TestRunner.structuredLogger.logData("buffering_on"); 361 }; 362 363 TestRunner.log = function (msg) { 364 if (TestRunner.logEnabled) { 365 TestRunner.structuredLogger.info(msg); 366 } else { 367 dump(msg + "\n"); 368 } 369 }; 370 371 TestRunner.error = function (msg) { 372 if (TestRunner.logEnabled) { 373 TestRunner.structuredLogger.error(msg); 374 } else { 375 dump(msg + "\n"); 376 TestRunner.failureHandler(); 377 } 378 }; 379 380 TestRunner.failureHandler = function () { 381 if (TestRunner.runUntilFailure) { 382 TestRunner._haltTests = true; 383 } 384 385 if (TestRunner.debugOnFailure) { 386 // You've hit this line because you requested to break into the 387 // debugger upon a testcase failure on your test run. 388 // eslint-disable-next-line no-debugger 389 debugger; 390 } 391 }; 392 393 /** 394 * Toggle element visibility 395 */ 396 TestRunner._toggle = function (el) { 397 if (el.className == "noshow") { 398 el.className = ""; 399 el.style.cssText = ""; 400 } else { 401 el.className = "noshow"; 402 el.style.cssText = "width:0px; height:0px; border:0px;"; 403 } 404 }; 405 406 /** 407 * Creates the iframe that contains a test 408 */ 409 TestRunner._makeIframe = function (url, retry) { 410 var iframe = $("testframe"); 411 if ( 412 url != "about:blank" && 413 (("hasFocus" in document && !document.hasFocus()) || 414 ("activeElement" in document && document.activeElement != iframe)) 415 ) { 416 contentAsyncEvent("Focus"); 417 window.focus(); 418 SpecialPowers.focus(); 419 iframe.focus(); 420 if (retry < 3) { 421 window.setTimeout(function () { 422 TestRunner._makeIframe(url, retry + 1); 423 }); 424 return; 425 } 426 427 TestRunner.structuredLogger.info( 428 "Error: Unable to restore focus, expect failures and timeouts." 429 ); 430 } 431 window.scrollTo(0, $("indicator").offsetTop); 432 try { 433 let urlObj = new URL(url); 434 if (TestRunner.xOriginTests) { 435 // The test will run in a xorigin iframe, so we pass in additional test params in the 436 // URL since the content process won't be able to access them from the parentRunner 437 // directly. 438 let params = TestRunner.getParameterInfo(); 439 urlObj.searchParams.append( 440 "currentTestURL", 441 urlObj.pathname.replace("/tests/", "") 442 ); 443 urlObj.searchParams.append("closeWhenDone", params.closeWhenDone); 444 urlObj.searchParams.append("showTestReport", TestRunner.showTestReport); 445 urlObj.searchParams.append("expected", TestRunner.expected); 446 iframe.src = urlObj.href; 447 } else { 448 iframe.src = url; 449 } 450 } catch { 451 // If the provided `url` is not a valid URL (i.e. doesn't include a protocol) 452 // then the new URL() constructor will raise a TypeError. This is expected in the 453 // usual case (i.e. non-xorigin iFrame tests) so set the URL in the usual way. 454 iframe.src = url; 455 } 456 iframe.name = url; 457 iframe.width = "500"; 458 }; 459 460 /** 461 * Returns the current test URL. 462 * We use this to tell whether the test has navigated to another test without 463 * being finished first. 464 */ 465 TestRunner.getLoadedTestURL = function () { 466 if (!testInXOriginFrame()) { 467 var prefix = ""; 468 // handle mochitest-chrome URIs 469 if ($("testframe").contentWindow.location.protocol == "chrome:") { 470 prefix = "chrome://mochitests"; 471 } 472 return prefix + $("testframe").contentWindow.location.pathname; 473 } 474 return TestRunner.currentTestURL; 475 }; 476 477 TestRunner.setParameterInfo = function (params) { 478 this._params = params; 479 }; 480 481 TestRunner.getParameterInfo = function () { 482 return this._params; 483 }; 484 485 /** 486 * Print information about which prefs are set. 487 * This is used to help validate that the tests are actually 488 * running in the expected context. 489 */ 490 TestRunner.dumpPrefContext = function () { 491 let prefs = ["fission.autostart"]; 492 493 let message = ["Dumping test context:"]; 494 prefs.forEach(function formatPref(pref) { 495 let val = SpecialPowers.getBoolPref(pref); 496 message.push(pref + "=" + val); 497 }); 498 TestRunner.structuredLogger.info(message.join("\n ")); 499 }; 500 501 /** 502 * TestRunner entry point. 503 * 504 * The arguments are the URLs of the test to be ran. 505 * 506 */ 507 TestRunner.runTests = function (/*url...*/) { 508 TestRunner.structuredLogger.info("SimpleTest START"); 509 TestRunner.dumpPrefContext(); 510 TestRunner.originalTestURL = $("current-test").innerHTML; 511 512 SpecialPowers.registerProcessCrashObservers(); 513 514 // Initialize code coverage 515 if (TestRunner.jscovDirPrefix != "") { 516 var { CoverageCollector } = SpecialPowers.ChromeUtils.importESModule( 517 "resource://testing-common/CoverageUtils.sys.mjs" 518 ); 519 coverageCollector = new CoverageCollector(TestRunner.jscovDirPrefix); 520 } 521 522 SpecialPowers.requestResetCoverageCounters().then(() => { 523 TestRunner._urls = flattenArguments(arguments); 524 525 var singleTestRun = this._urls.length <= 1 && TestRunner.repeat <= 1; 526 TestRunner.showTestReport = singleTestRun; 527 var frame = $("testframe"); 528 frame.src = ""; 529 if (singleTestRun) { 530 // Can't use document.body because this runs in a XUL doc as well... 531 var body = document.getElementsByTagName("body")[0]; 532 body.setAttribute("singletest", "true"); 533 frame.removeAttribute("scrolling"); 534 } 535 TestRunner._checkForHangs(); 536 TestRunner.runNextTest(); 537 }); 538 }; 539 540 /** 541 * Used for running a set of tests in a loop for debugging purposes 542 * Takes an array of URLs 543 */ 544 TestRunner.resetTests = function (listURLs) { 545 TestRunner._currentTest = 0; 546 // Reset our "Current-test" line - functionality depends on it 547 $("current-test").innerHTML = TestRunner.originalTestURL; 548 if (TestRunner.logEnabled) { 549 TestRunner.structuredLogger.info( 550 "SimpleTest START Loop " + TestRunner._currentLoop 551 ); 552 } 553 554 TestRunner._urls = listURLs; 555 $("testframe").src = ""; 556 TestRunner._checkForHangs(); 557 TestRunner.runNextTest(); 558 }; 559 560 TestRunner.getNextUrl = function () { 561 var url = ""; 562 // sometimes we have a subtest/harness which doesn't use a manifest 563 if ( 564 TestRunner._urls[TestRunner._currentTest] instanceof Object && 565 "test" in TestRunner._urls[TestRunner._currentTest] 566 ) { 567 url = TestRunner._urls[TestRunner._currentTest].test.url; 568 TestRunner.expected = 569 TestRunner._urls[TestRunner._currentTest].test.expected; 570 } else { 571 url = TestRunner._urls[TestRunner._currentTest]; 572 TestRunner.expected = "pass"; 573 } 574 return url; 575 }; 576 577 /** 578 * Run the next test. If no test remains, calls onComplete(). 579 */ 580 TestRunner._haltTests = false; 581 async function _runNextTest() { 582 if ( 583 TestRunner._currentTest < TestRunner._urls.length && 584 !TestRunner._haltTests 585 ) { 586 var url = TestRunner.getNextUrl(); 587 TestRunner.currentTestURL = url; 588 589 $("current-test-path").innerHTML = url; 590 591 TestRunner._currentTestStartTimestamp = SpecialPowers.ChromeUtils.now(); 592 TestRunner._currentTestStartTime = new Date().valueOf(); 593 TestRunner._timeoutFactor = 1; 594 TestRunner._expectedMinAsserts = 0; 595 TestRunner._expectedMaxAsserts = 0; 596 597 TestRunner.structuredLogger.testStart(url); 598 599 if (TestRunner._urls[TestRunner._currentTest].test.allow_xul_xbl) { 600 await SpecialPowers.pushPermissions([ 601 { type: "allowXULXBL", allow: true, context: "http://mochi.test:8888" }, 602 { type: "allowXULXBL", allow: true, context: "http://example.org" }, 603 ]); 604 } 605 if (TestRunner._urls[TestRunner._currentTest].test.https_first_disabled) { 606 await SpecialPowers.pushPrefEnv({ 607 set: [["dom.security.https_first", false]], 608 }); 609 } 610 TestRunner._makeIframe(url, 0); 611 } else { 612 $("current-test").innerHTML = "<b>Finished</b>"; 613 // Only unload the last test to run if we're running more than one test. 614 if (TestRunner._urls.length > 1) { 615 TestRunner._makeIframe("about:blank", 0); 616 } 617 618 var passCount = parseInt($("pass-count").innerHTML, 10); 619 var failCount = parseInt($("fail-count").innerHTML, 10); 620 var todoCount = parseInt($("todo-count").innerHTML, 10); 621 622 if (passCount === 0 && failCount === 0 && todoCount === 0) { 623 // No |$('testframe').contentWindow|, so manually update: ... 624 // ... the log, 625 TestRunner.structuredLogger.error( 626 "TEST-UNEXPECTED-FAIL | SimpleTest/TestRunner.js | No checks actually run" 627 ); 628 // ... the count, 629 $("fail-count").innerHTML = 1; 630 // ... the indicator. 631 var indicator = $("indicator"); 632 indicator.innerHTML = "Status: Fail (No checks actually run)"; 633 indicator.style.backgroundColor = "red"; 634 } 635 636 let e10sMode = SpecialPowers.isMainProcess() ? "non-e10s" : "e10s"; 637 638 TestRunner.structuredLogger.info("TEST-START | Shutdown"); 639 TestRunner.structuredLogger.info("Passed: " + passCount); 640 TestRunner.structuredLogger.info("Failed: " + failCount); 641 TestRunner.structuredLogger.info("Todo: " + todoCount); 642 TestRunner.structuredLogger.info("Mode: " + e10sMode); 643 TestRunner.structuredLogger.info( 644 "Slowest: " + 645 TestRunner.slowestTestTime + 646 "ms - " + 647 TestRunner.slowestTestURL 648 ); 649 650 // If we are looping, don't send this cause it closes the log file, 651 // also don't unregister the crash observers until we're done. 652 if (TestRunner.repeat === 0) { 653 SpecialPowers.unregisterProcessCrashObservers(); 654 TestRunner.structuredLogger.info("SimpleTest FINISHED"); 655 } 656 657 if (TestRunner.repeat === 0 && TestRunner.onComplete) { 658 TestRunner.onComplete(); 659 } 660 661 if ( 662 TestRunner._currentLoop <= TestRunner.repeat && 663 !TestRunner._haltTests 664 ) { 665 TestRunner._currentLoop++; 666 TestRunner.resetTests(TestRunner._urls); 667 TestRunner._loopIsRestarting = true; 668 } else { 669 // Loops are finished 670 if (TestRunner.logEnabled) { 671 TestRunner.structuredLogger.info( 672 "TEST-INFO | Ran " + TestRunner._currentLoop + " Loops" 673 ); 674 TestRunner.structuredLogger.info("SimpleTest FINISHED"); 675 } 676 677 if (TestRunner.onComplete) { 678 TestRunner.onComplete(); 679 } 680 } 681 TestRunner.generateFailureList(); 682 683 if (TestRunner.jscovDirPrefix != "") { 684 coverageCollector.finalize(); 685 } 686 } 687 } 688 TestRunner.runNextTest = _runNextTest; 689 690 TestRunner.expectChildProcessCrash = function () { 691 TestRunner._expectingProcessCrash = true; 692 }; 693 694 /** 695 * This stub is called by SimpleTest when a test is finished. 696 */ 697 TestRunner.testFinished = function (tests) { 698 // Need to track subtests recorded here separately or else they'll 699 // trigger the `result after SimpleTest.finish()` error. 700 var extraTests = []; 701 var result = "OK"; 702 703 // Prevent a test from calling finish() multiple times before we 704 // have a chance to unload it. 705 if ( 706 TestRunner._currentTest == TestRunner._lastTestFinished && 707 !TestRunner._loopIsRestarting 708 ) { 709 TestRunner.structuredLogger.testEnd( 710 TestRunner.currentTestURL, 711 "ERROR", 712 "OK", 713 "called finish() multiple times" 714 ); 715 TestRunner.updateUI([{ result: false }]); 716 return; 717 } 718 719 if (TestRunner.jscovDirPrefix != "") { 720 coverageCollector.recordTestCoverage(TestRunner.currentTestURL); 721 } 722 723 SpecialPowers.requestDumpCoverageCounters().then(() => { 724 TestRunner._lastTestFinished = TestRunner._currentTest; 725 TestRunner._loopIsRestarting = false; 726 727 // TODO : replace this by a function that returns the mem data as an object 728 // that's dumped later with the test_end message 729 MemoryStats.dump( 730 TestRunner._currentTest, 731 TestRunner.currentTestURL, 732 TestRunner.dumpOutputDirectory, 733 TestRunner.dumpAboutMemoryAfterTest, 734 TestRunner.dumpDMDAfterTest 735 ); 736 737 async function cleanUpCrashDumpFiles() { 738 if ( 739 !(await SpecialPowers.removeExpectedCrashDumpFiles( 740 TestRunner._expectingProcessCrash 741 )) 742 ) { 743 let subtest = "expected-crash-dump-missing"; 744 TestRunner.structuredLogger.testStatus( 745 TestRunner.currentTestURL, 746 subtest, 747 "ERROR", 748 "PASS", 749 "This test did not leave any crash dumps behind, but we were expecting some!" 750 ); 751 extraTests.push({ name: subtest, result: false }); 752 result = "ERROR"; 753 } 754 755 var unexpectedCrashDumpFiles = SpecialPowers.unwrap( 756 await SpecialPowers.findUnexpectedCrashDumpFiles() 757 ); 758 TestRunner._expectingProcessCrash = false; 759 if (unexpectedCrashDumpFiles.length) { 760 let subtest = "unexpected-crash-dump-found"; 761 TestRunner.structuredLogger.testStatus( 762 TestRunner.currentTestURL, 763 subtest, 764 "ERROR", 765 "PASS", 766 "This test left crash dumps behind, but we " + 767 "weren't expecting it to!", 768 null, 769 { unexpected_crashdump_files: unexpectedCrashDumpFiles } 770 ); 771 extraTests.push({ name: subtest, result: false }); 772 result = "CRASH"; 773 unexpectedCrashDumpFiles.sort().forEach(function (aFilename) { 774 TestRunner.structuredLogger.info( 775 "Found unexpected crash dump file " + aFilename + "." 776 ); 777 }); 778 } 779 780 if (TestRunner.cleanupCrashes) { 781 if (await SpecialPowers.removePendingCrashDumpFiles()) { 782 TestRunner.structuredLogger.info( 783 "This test left pending crash dumps" 784 ); 785 } 786 } 787 } 788 789 function runNextTest() { 790 if (TestRunner.currentTestURL != TestRunner.getLoadedTestURL()) { 791 TestRunner.structuredLogger.testStatus( 792 TestRunner.currentTestURL, 793 TestRunner.getLoadedTestURL(), 794 "FAIL", 795 "PASS", 796 "finished in a non-clean fashion, probably" + 797 " because it didn't call SimpleTest.finish()", 798 { loaded_test_url: TestRunner.getLoadedTestURL() } 799 ); 800 extraTests.push({ name: "clean-finish", result: false }); 801 result = result != "CRASH" ? "ERROR" : result; 802 } 803 804 SpecialPowers.addProfilerMarker( 805 "TestRunner", 806 { category: "Test", startTime: TestRunner._currentTestStartTimestamp }, 807 TestRunner.currentTestURL 808 ); 809 var runtime = new Date().valueOf() - TestRunner._currentTestStartTime; 810 811 if ( 812 TestRunner.slowestTestTime < runtime && 813 TestRunner._timeoutFactor >= 1 814 ) { 815 TestRunner.slowestTestTime = runtime; 816 TestRunner.slowestTestURL = TestRunner.currentTestURL; 817 } 818 819 TestRunner.updateUI(tests.concat(extraTests)); 820 821 // Don't show the interstitial if we just run one test with no repeats: 822 if (TestRunner._urls.length == 1 && TestRunner.repeat <= 1) { 823 TestRunner.testUnloaded(result, runtime); 824 return; 825 } 826 827 var interstitialURL; 828 if ( 829 !testInXOriginFrame() && 830 $("testframe").contentWindow.location.protocol == "chrome:" 831 ) { 832 interstitialURL = 833 "tests/SimpleTest/iframe-between-tests.html?result=" + 834 result + 835 "&runtime=" + 836 runtime; 837 } else { 838 interstitialURL = 839 "/tests/SimpleTest/iframe-between-tests.html?result=" + 840 result + 841 "&runtime=" + 842 runtime; 843 } 844 // check if there were test run after SimpleTest.finish, which should never happen 845 if (!testInXOriginFrame()) { 846 $("testframe").contentWindow.addEventListener("unload", function () { 847 var testwin = $("testframe").contentWindow; 848 if (testwin.SimpleTest) { 849 if (typeof testwin.SimpleTest.testsLength === "undefined") { 850 TestRunner.structuredLogger.error( 851 "TEST-UNEXPECTED-FAIL | " + 852 TestRunner.currentTestURL + 853 " fired an unload callback with missing test data," + 854 " possibly due to the test navigating or reloading" 855 ); 856 TestRunner.updateUI([{ result: false }]); 857 } else if ( 858 testwin.SimpleTest._tests.length != testwin.SimpleTest.testsLength 859 ) { 860 var didReportError = false; 861 var wrongtestlength = 862 testwin.SimpleTest._tests.length - 863 testwin.SimpleTest.testsLength; 864 var wrongtestname = ""; 865 for (var i = 0; i < wrongtestlength; i++) { 866 wrongtestname = 867 testwin.SimpleTest._tests[testwin.SimpleTest.testsLength + i] 868 .name; 869 TestRunner.structuredLogger.error( 870 "TEST-UNEXPECTED-FAIL | " + 871 TestRunner.currentTestURL + 872 " logged result after SimpleTest.finish(): " + 873 wrongtestname 874 ); 875 didReportError = true; 876 } 877 if (!didReportError) { 878 // This clause shouldn't be reachable, but if we somehow get 879 // here (e.g. if wrongtestlength is somehow negative), it's 880 // important that we log *something* for the { result: false } 881 // test-failure that we're about to post. 882 TestRunner.structuredLogger.error( 883 "TEST-UNEXPECTED-FAIL | " + 884 TestRunner.currentTestURL + 885 " hit an unexpected condition when checking for" + 886 " logged results after SimpleTest.finish()" 887 ); 888 } 889 TestRunner.updateUI([{ result: false }]); 890 } 891 } 892 }); 893 } 894 TestRunner._makeIframe(interstitialURL, 0); 895 } 896 897 SpecialPowers.executeAfterFlushingMessageQueue(async function () { 898 await SpecialPowers.waitForCrashes(TestRunner._expectingProcessCrash); 899 await cleanUpCrashDumpFiles(); 900 await SpecialPowers.flushPermissions(); 901 await SpecialPowers.flushPrefEnv(); 902 SpecialPowers.cleanupAllClipboard(window); 903 runNextTest(); 904 }); 905 }); 906 }; 907 908 /** 909 * This stub is called by XOrigin Tests to report assertion count. 910 */ 911 TestRunner._xoriginAssertionCount = 0; 912 TestRunner.addAssertionCount = function (count) { 913 if (!testInXOriginFrame()) { 914 TestRunner.error( 915 `addAssertionCount should only be called by a cross origin test` 916 ); 917 return; 918 } 919 920 if (testInDifferentProcess()) { 921 TestRunner._xoriginAssertionCount += count; 922 } 923 }; 924 925 TestRunner.testUnloaded = function (result, runtime) { 926 // If we're in a debug build, check assertion counts. This code is 927 // similar to the code in Tester_nextTest in browser-test.js used 928 // for browser-chrome mochitests. 929 if (SpecialPowers.isDebugBuild) { 930 var newAssertionCount = 931 SpecialPowers.assertionCount() + TestRunner._xoriginAssertionCount; 932 var numAsserts = newAssertionCount - TestRunner._lastAssertionCount; 933 TestRunner._lastAssertionCount = newAssertionCount; 934 935 var max = TestRunner._expectedMaxAsserts; 936 var min = TestRunner._expectedMinAsserts; 937 if (Array.isArray(TestRunner.expected)) { 938 // Accumulate all assertion counts recorded in the failure pattern file. 939 let additionalAsserts = TestRunner.expected.reduce( 940 (acc, [pat, count]) => { 941 return pat == "ASSERTION" ? acc + count : acc; 942 }, 943 0 944 ); 945 min += additionalAsserts; 946 max += additionalAsserts; 947 } 948 949 TestRunner.structuredLogger.assertionCount( 950 TestRunner.currentTestURL, 951 numAsserts, 952 min, 953 max 954 ); 955 956 if (numAsserts < min || numAsserts > max) { 957 result = "ERROR"; 958 959 var direction = "more"; 960 var target = max; 961 if (numAsserts < min) { 962 direction = "less"; 963 target = min; 964 } 965 TestRunner.structuredLogger.testStatus( 966 TestRunner.currentTestURL, 967 "Assertion Count", 968 "ERROR", 969 "PASS", 970 numAsserts + 971 " is " + 972 direction + 973 " than expected " + 974 target + 975 " assertions" 976 ); 977 978 // reset result so we don't print a second error on test-end 979 result = "OK"; 980 } 981 } 982 983 TestRunner.structuredLogger.testEnd( 984 TestRunner.currentTestURL, 985 result, 986 "OK", 987 "Finished in " + runtime + "ms", 988 { runtime } 989 ); 990 991 // Always do this, so we can "reset" preferences between tests. 992 // Note: this is for mochitest-plain only; browser tests do not 993 // unconditionally reset between tests, see 994 // checkPreferencesAfterTest in testing/mochitest/browser-test.js 995 SpecialPowers.comparePrefsToBaseline( 996 TestRunner.ignorePrefs, 997 TestRunner.verifyPrefsNextTest 998 ); 999 }; 1000 1001 TestRunner.verifyPrefsNextTest = function (p) { 1002 if (TestRunner.comparePrefs) { 1003 let prefs = Array.from(SpecialPowers.Cu.waiveXrays(p), x => 1004 SpecialPowers.unwrapIfWrapped(SpecialPowers.Cu.unwaiveXrays(x)) 1005 ); 1006 prefs.forEach(pr => 1007 TestRunner.structuredLogger.error( 1008 "TEST-UNEXPECTED-FAIL | " + 1009 TestRunner.currentTestURL + 1010 " | changed preference: " + 1011 pr 1012 ) 1013 ); 1014 } 1015 TestRunner.doNextTest(); 1016 }; 1017 1018 TestRunner.doNextTest = function () { 1019 TestRunner._currentTest++; 1020 if (TestRunner.runSlower) { 1021 setTimeout(TestRunner.runNextTest, 1000); 1022 } else { 1023 TestRunner.runNextTest(); 1024 } 1025 }; 1026 1027 /** 1028 * Get the results. 1029 */ 1030 TestRunner.countResults = function (tests) { 1031 var nOK = 0; 1032 var nNotOK = 0; 1033 var nTodo = 0; 1034 for (var i = 0; i < tests.length; ++i) { 1035 var test = tests[i]; 1036 if (test.todo && !test.result) { 1037 nTodo++; 1038 } else if (test.result && !test.todo) { 1039 nOK++; 1040 } else { 1041 nNotOK++; 1042 } 1043 } 1044 return { OK: nOK, notOK: nNotOK, todo: nTodo }; 1045 }; 1046 1047 /** 1048 * Print out table of any error messages found during looped run 1049 */ 1050 TestRunner.displayLoopErrors = function (tableName, tests) { 1051 if (TestRunner.countResults(tests).notOK > 0) { 1052 var table = $(tableName); 1053 var curtest; 1054 if (!table.rows.length) { 1055 //if table headers are not yet generated, make them 1056 var row = table.insertRow(table.rows.length); 1057 var cell = row.insertCell(0); 1058 var textNode = document.createTextNode("Test File Name:"); 1059 cell.appendChild(textNode); 1060 cell = row.insertCell(1); 1061 textNode = document.createTextNode("Test:"); 1062 cell.appendChild(textNode); 1063 cell = row.insertCell(2); 1064 textNode = document.createTextNode("Error message:"); 1065 cell.appendChild(textNode); 1066 } 1067 1068 //find the broken test 1069 for (var testnum in tests) { 1070 curtest = tests[testnum]; 1071 if ( 1072 !( 1073 (curtest.todo && !curtest.result) || 1074 (curtest.result && !curtest.todo) 1075 ) 1076 ) { 1077 //this is a failed test or the result of todo test. Display the related message 1078 row = table.insertRow(table.rows.length); 1079 cell = row.insertCell(0); 1080 textNode = document.createTextNode(TestRunner.currentTestURL); 1081 cell.appendChild(textNode); 1082 cell = row.insertCell(1); 1083 textNode = document.createTextNode(curtest.name); 1084 cell.appendChild(textNode); 1085 cell = row.insertCell(2); 1086 textNode = document.createTextNode(curtest.diag ? curtest.diag : ""); 1087 cell.appendChild(textNode); 1088 } 1089 } 1090 } 1091 }; 1092 1093 TestRunner.updateUI = function (tests) { 1094 var results = TestRunner.countResults(tests); 1095 var passCount = parseInt($("pass-count").innerHTML) + results.OK; 1096 var failCount = parseInt($("fail-count").innerHTML) + results.notOK; 1097 var todoCount = parseInt($("todo-count").innerHTML) + results.todo; 1098 $("pass-count").innerHTML = passCount; 1099 $("fail-count").innerHTML = failCount; 1100 $("todo-count").innerHTML = todoCount; 1101 1102 // Set the top Green/Red bar 1103 var indicator = $("indicator"); 1104 if (failCount > 0) { 1105 indicator.innerHTML = "Status: Fail"; 1106 indicator.style.backgroundColor = "red"; 1107 } else if (passCount > 0) { 1108 indicator.innerHTML = "Status: Pass"; 1109 indicator.style.backgroundColor = "#0d0"; 1110 } else { 1111 indicator.innerHTML = "Status: ToDo"; 1112 indicator.style.backgroundColor = "orange"; 1113 } 1114 1115 // Set the table values 1116 var trID = "tr-" + $("current-test-path").innerHTML; 1117 var row = $(trID); 1118 1119 // Only update the row if it actually exists (autoUI) 1120 if (row != null) { 1121 var tds = row.getElementsByTagName("td"); 1122 tds[0].style.backgroundColor = "#0d0"; 1123 tds[0].innerHTML = parseInt(tds[0].innerHTML) + parseInt(results.OK); 1124 tds[1].style.backgroundColor = results.notOK > 0 ? "red" : "#0d0"; 1125 tds[1].innerHTML = parseInt(tds[1].innerHTML) + parseInt(results.notOK); 1126 tds[2].style.backgroundColor = results.todo > 0 ? "orange" : "#0d0"; 1127 tds[2].innerHTML = parseInt(tds[2].innerHTML) + parseInt(results.todo); 1128 } 1129 1130 //if we ran in a loop, display any found errors 1131 if (TestRunner.repeat > 0) { 1132 TestRunner.displayLoopErrors("fail-table", tests); 1133 } 1134 }; 1135 1136 // XOrigin Tests 1137 // If "--enable-xorigin-tests" is set, mochitests are run in a cross origin iframe. 1138 // The parent process will run at http://mochi.xorigin-test:8888", and individual 1139 // mochitests will be launched in a cross-origin iframe at http://mochi.test:8888. 1140 1141 var xOriginDispatchMap = { 1142 runner: TestRunner, 1143 logger: TestRunner.structuredLogger, 1144 addFailedTest: TestRunner.addFailedTest, 1145 expectAssertions: TestRunner.expectAssertions, 1146 expectChildProcessCrash: TestRunner.expectChildProcessCrash, 1147 requestLongerTimeout: TestRunner.requestLongerTimeout, 1148 "structuredLogger.deactivateBuffering": 1149 TestRunner.structuredLogger.deactivateBuffering, 1150 "structuredLogger.activateBuffering": 1151 TestRunner.structuredLogger.activateBuffering, 1152 "structuredLogger.testStatus": TestRunner.structuredLogger.testStatus, 1153 "structuredLogger.info": TestRunner.structuredLogger.info, 1154 "structuredLogger.warning": TestRunner.structuredLogger.warning, 1155 "structuredLogger.error": TestRunner.structuredLogger.error, 1156 testFinished: TestRunner.testFinished, 1157 addAssertionCount: TestRunner.addAssertionCount, 1158 }; 1159 1160 function xOriginTestRunnerHandler(event) { 1161 if (event.data.harnessType != "SimpleTest") { 1162 return; 1163 } 1164 // Handles messages from xOriginRunner in SimpleTest.js. 1165 if (event.data.command in xOriginDispatchMap) { 1166 xOriginDispatchMap[event.data.command].apply( 1167 xOriginDispatchMap[event.data.applyOn], 1168 event.data.params 1169 ); 1170 } else { 1171 TestRunner.error(`Command ${event.data.command} not found 1172 in xOriginDispatchMap`); 1173 } 1174 } 1175 1176 TestRunner.setXOriginEventHandler = function () { 1177 window.addEventListener("message", xOriginTestRunnerHandler); 1178 };