1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/mochitest/browser-test.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,946 @@ 1.4 +/* -*- js-indent-level: 2; tab-width: 2; indent-tabs-mode: nil -*- */ 1.5 +// Test timeout (seconds) 1.6 +var gTimeoutSeconds = 45; 1.7 +var gConfig; 1.8 + 1.9 +if (Cc === undefined) { 1.10 + var Cc = Components.classes; 1.11 + var Ci = Components.interfaces; 1.12 + var Cu = Components.utils; 1.13 +} 1.14 + 1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.16 +Cu.import("resource://gre/modules/Task.jsm"); 1.17 + 1.18 +XPCOMUtils.defineLazyModuleGetter(this, "Services", 1.19 + "resource://gre/modules/Services.jsm"); 1.20 + 1.21 +XPCOMUtils.defineLazyModuleGetter(this, "BrowserNewTabPreloader", 1.22 + "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader"); 1.23 + 1.24 +XPCOMUtils.defineLazyModuleGetter(this, "CustomizationTabPreloader", 1.25 + "resource:///modules/CustomizationTabPreloader.jsm", "CustomizationTabPreloader"); 1.26 + 1.27 +const SIMPLETEST_OVERRIDES = 1.28 + ["ok", "is", "isnot", "ise", "todo", "todo_is", "todo_isnot", "info", "expectAssertions"]; 1.29 + 1.30 +window.addEventListener("load", testOnLoad, false); 1.31 + 1.32 +function testOnLoad() { 1.33 + window.removeEventListener("load", testOnLoad, false); 1.34 + 1.35 + gConfig = readConfig(); 1.36 + if (gConfig.testRoot == "browser" || 1.37 + gConfig.testRoot == "metro" || 1.38 + gConfig.testRoot == "webapprtChrome") { 1.39 + // Make sure to launch the test harness for the first opened window only 1.40 + var prefs = Services.prefs; 1.41 + if (prefs.prefHasUserValue("testing.browserTestHarness.running")) 1.42 + return; 1.43 + 1.44 + prefs.setBoolPref("testing.browserTestHarness.running", true); 1.45 + 1.46 + if (prefs.prefHasUserValue("testing.browserTestHarness.timeout")) 1.47 + gTimeoutSeconds = prefs.getIntPref("testing.browserTestHarness.timeout"); 1.48 + 1.49 + var sstring = Cc["@mozilla.org/supports-string;1"]. 1.50 + createInstance(Ci.nsISupportsString); 1.51 + sstring.data = location.search; 1.52 + 1.53 + Services.ww.openWindow(window, "chrome://mochikit/content/browser-harness.xul", "browserTest", 1.54 + "chrome,centerscreen,dialog=no,resizable,titlebar,toolbar=no,width=800,height=600", sstring); 1.55 + } else { 1.56 + // This code allows us to redirect without requiring specialpowers for chrome and a11y tests. 1.57 + let messageHandler = function(m) { 1.58 + messageManager.removeMessageListener("chromeEvent", messageHandler); 1.59 + var url = m.json.data; 1.60 + 1.61 + // Window is the [ChromeWindow] for messageManager, so we need content.window 1.62 + // Currently chrome tests are run in a content window instead of a ChromeWindow 1.63 + var webNav = content.window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) 1.64 + .getInterface(Components.interfaces.nsIWebNavigation); 1.65 + webNav.loadURI(url, null, null, null, null); 1.66 + }; 1.67 + 1.68 + var listener = 'data:,function doLoad(e) { var data=e.detail&&e.detail.data;removeEventListener("contentEvent", function (e) { doLoad(e); }, false, true);sendAsyncMessage("chromeEvent", {"data":data}); };addEventListener("contentEvent", function (e) { doLoad(e); }, false, true);'; 1.69 + messageManager.loadFrameScript(listener, true); 1.70 + messageManager.addMessageListener("chromeEvent", messageHandler); 1.71 + } 1.72 + if (gConfig.e10s) { 1.73 + e10s_init(); 1.74 + } 1.75 +} 1.76 + 1.77 +function Tester(aTests, aDumper, aCallback) { 1.78 + this.dumper = aDumper; 1.79 + this.tests = aTests; 1.80 + this.callback = aCallback; 1.81 + this.openedWindows = {}; 1.82 + this.openedURLs = {}; 1.83 + 1.84 + this._scriptLoader = Services.scriptloader; 1.85 + this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", this.EventUtils); 1.86 + var simpleTestScope = {}; 1.87 + this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js", simpleTestScope); 1.88 + this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js", simpleTestScope); 1.89 + this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromePowers.js", simpleTestScope); 1.90 + this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SimpleTest.js", simpleTestScope); 1.91 + this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/MemoryStats.js", simpleTestScope); 1.92 + this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", simpleTestScope); 1.93 + this.SimpleTest = simpleTestScope.SimpleTest; 1.94 + this.MemoryStats = simpleTestScope.MemoryStats; 1.95 + this.Task = Task; 1.96 + this.Promise = Components.utils.import("resource://gre/modules/Promise.jsm", null).Promise; 1.97 + this.Assert = Components.utils.import("resource://testing-common/Assert.jsm", null).Assert; 1.98 + 1.99 + this.SimpleTestOriginal = {}; 1.100 + SIMPLETEST_OVERRIDES.forEach(m => { 1.101 + this.SimpleTestOriginal[m] = this.SimpleTest[m]; 1.102 + }); 1.103 + 1.104 + this._uncaughtErrorObserver = function({message, date, fileName, stack, lineNumber}) { 1.105 + let text = "Once bug 991040 has landed, THIS ERROR WILL CAUSE A TEST FAILURE.\n" + message; 1.106 + let error = text; 1.107 + if (fileName || lineNumber) { 1.108 + error = { 1.109 + fileName: fileName, 1.110 + lineNumber: lineNumber, 1.111 + message: text, 1.112 + toString: function() { 1.113 + return text; 1.114 + } 1.115 + }; 1.116 + } 1.117 + this.currentTest.addResult( 1.118 + new testResult( 1.119 + /*success*/ true, 1.120 + /*name*/"A promise chain failed to handle a rejection", 1.121 + /*error*/error, 1.122 + /*known*/true, 1.123 + /*stack*/stack)); 1.124 + }.bind(this); 1.125 +} 1.126 +Tester.prototype = { 1.127 + EventUtils: {}, 1.128 + SimpleTest: {}, 1.129 + Task: null, 1.130 + Promise: null, 1.131 + Assert: null, 1.132 + 1.133 + repeat: 0, 1.134 + runUntilFailure: false, 1.135 + checker: null, 1.136 + currentTestIndex: -1, 1.137 + lastStartTime: null, 1.138 + openedWindows: null, 1.139 + lastAssertionCount: 0, 1.140 + 1.141 + get currentTest() { 1.142 + return this.tests[this.currentTestIndex]; 1.143 + }, 1.144 + get done() { 1.145 + return this.currentTestIndex == this.tests.length - 1; 1.146 + }, 1.147 + 1.148 + start: function Tester_start() { 1.149 + // Check whether this window is ready to run tests. 1.150 + if (window.BrowserChromeTest) { 1.151 + BrowserChromeTest.runWhenReady(this.actuallyStart.bind(this)); 1.152 + return; 1.153 + } 1.154 + this.actuallyStart(); 1.155 + }, 1.156 + 1.157 + actuallyStart: function Tester_actuallyStart() { 1.158 + //if testOnLoad was not called, then gConfig is not defined 1.159 + if (!gConfig) 1.160 + gConfig = readConfig(); 1.161 + 1.162 + if (gConfig.runUntilFailure) 1.163 + this.runUntilFailure = true; 1.164 + 1.165 + if (gConfig.repeat) 1.166 + this.repeat = gConfig.repeat; 1.167 + 1.168 + this.dumper.dump("*** Start BrowserChrome Test Results ***\n"); 1.169 + Services.console.registerListener(this); 1.170 + Services.obs.addObserver(this, "chrome-document-global-created", false); 1.171 + Services.obs.addObserver(this, "content-document-global-created", false); 1.172 + this._globalProperties = Object.keys(window); 1.173 + this._globalPropertyWhitelist = [ 1.174 + "navigator", "constructor", "top", 1.175 + "Application", 1.176 + "__SS_tabsToRestore", "__SSi", 1.177 + "webConsoleCommandController", 1.178 + ]; 1.179 + 1.180 + this.Promise.Debugging.clearUncaughtErrorObservers(); 1.181 + this.Promise.Debugging.addUncaughtErrorObserver(this._uncaughtErrorObserver); 1.182 + 1.183 + if (this.tests.length) 1.184 + this.nextTest(); 1.185 + else 1.186 + this.finish(); 1.187 + }, 1.188 + 1.189 + waitForWindowsState: function Tester_waitForWindowsState(aCallback) { 1.190 + let timedOut = this.currentTest && this.currentTest.timedOut; 1.191 + let baseMsg = timedOut ? "Found a {elt} after previous test timed out" 1.192 + : this.currentTest ? "Found an unexpected {elt} at the end of test run" 1.193 + : "Found an unexpected {elt}"; 1.194 + 1.195 + // Remove stale tabs 1.196 + if (this.currentTest && window.gBrowser && gBrowser.tabs.length > 1) { 1.197 + while (gBrowser.tabs.length > 1) { 1.198 + let lastTab = gBrowser.tabContainer.lastChild; 1.199 + let msg = baseMsg.replace("{elt}", "tab") + 1.200 + ": " + lastTab.linkedBrowser.currentURI.spec; 1.201 + this.currentTest.addResult(new testResult(false, msg, "", false)); 1.202 + gBrowser.removeTab(lastTab); 1.203 + } 1.204 + } 1.205 + 1.206 + // Replace the last tab with a fresh one 1.207 + if (window.gBrowser) { 1.208 + gBrowser.addTab("about:blank", { skipAnimation: true }); 1.209 + gBrowser.removeCurrentTab(); 1.210 + gBrowser.stop(); 1.211 + } 1.212 + 1.213 + // Remove stale windows 1.214 + this.dumper.dump("TEST-INFO | checking window state\n"); 1.215 + let windowsEnum = Services.wm.getEnumerator(null); 1.216 + while (windowsEnum.hasMoreElements()) { 1.217 + let win = windowsEnum.getNext(); 1.218 + if (win != window && !win.closed && 1.219 + win.document.documentElement.getAttribute("id") != "browserTestHarness") { 1.220 + let type = win.document.documentElement.getAttribute("windowtype"); 1.221 + switch (type) { 1.222 + case "navigator:browser": 1.223 + type = "browser window"; 1.224 + break; 1.225 + case null: 1.226 + type = "unknown window"; 1.227 + break; 1.228 + } 1.229 + let msg = baseMsg.replace("{elt}", type); 1.230 + if (this.currentTest) 1.231 + this.currentTest.addResult(new testResult(false, msg, "", false)); 1.232 + else 1.233 + this.dumper.dump("TEST-UNEXPECTED-FAIL | (browser-test.js) | " + msg + "\n"); 1.234 + 1.235 + win.close(); 1.236 + } 1.237 + } 1.238 + 1.239 + // Make sure the window is raised before each test. 1.240 + this.SimpleTest.waitForFocus(aCallback); 1.241 + }, 1.242 + 1.243 + finish: function Tester_finish(aSkipSummary) { 1.244 + this.Promise.Debugging.flushUncaughtErrors(); 1.245 + 1.246 + var passCount = this.tests.reduce(function(a, f) a + f.passCount, 0); 1.247 + var failCount = this.tests.reduce(function(a, f) a + f.failCount, 0); 1.248 + var todoCount = this.tests.reduce(function(a, f) a + f.todoCount, 0); 1.249 + 1.250 + if (this.repeat > 0) { 1.251 + --this.repeat; 1.252 + this.currentTestIndex = -1; 1.253 + this.nextTest(); 1.254 + } 1.255 + else{ 1.256 + Services.console.unregisterListener(this); 1.257 + Services.obs.removeObserver(this, "chrome-document-global-created"); 1.258 + Services.obs.removeObserver(this, "content-document-global-created"); 1.259 + this.Promise.Debugging.clearUncaughtErrorObservers(); 1.260 + this.dumper.dump("\nINFO TEST-START | Shutdown\n"); 1.261 + 1.262 + if (this.tests.length) { 1.263 + this.dumper.dump("Browser Chrome Test Summary\n"); 1.264 + 1.265 + this.dumper.dump("\tPassed: " + passCount + "\n" + 1.266 + "\tFailed: " + failCount + "\n" + 1.267 + "\tTodo: " + todoCount + "\n"); 1.268 + } else { 1.269 + this.dumper.dump("TEST-UNEXPECTED-FAIL | (browser-test.js) | " + 1.270 + "No tests to run. Did you pass an invalid --test-path?\n"); 1.271 + } 1.272 + this.dumper.dump("\n*** End BrowserChrome Test Results ***\n"); 1.273 + 1.274 + this.dumper.done(); 1.275 + 1.276 + // Tests complete, notify the callback and return 1.277 + this.callback(this.tests); 1.278 + this.callback = null; 1.279 + this.tests = null; 1.280 + this.openedWindows = null; 1.281 + } 1.282 + }, 1.283 + 1.284 + haltTests: function Tester_haltTests() { 1.285 + // Do not run any further tests 1.286 + this.currentTestIndex = this.tests.length - 1; 1.287 + this.repeat = 0; 1.288 + }, 1.289 + 1.290 + observe: function Tester_observe(aSubject, aTopic, aData) { 1.291 + if (!aTopic) { 1.292 + this.onConsoleMessage(aSubject); 1.293 + } else if (this.currentTest) { 1.294 + this.onDocumentCreated(aSubject); 1.295 + } 1.296 + }, 1.297 + 1.298 + onDocumentCreated: function Tester_onDocumentCreated(aWindow) { 1.299 + let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.300 + .getInterface(Ci.nsIDOMWindowUtils); 1.301 + let outerID = utils.outerWindowID; 1.302 + let innerID = utils.currentInnerWindowID; 1.303 + 1.304 + if (!(outerID in this.openedWindows)) { 1.305 + this.openedWindows[outerID] = this.currentTest; 1.306 + } 1.307 + this.openedWindows[innerID] = this.currentTest; 1.308 + 1.309 + let url = aWindow.location.href || "about:blank"; 1.310 + this.openedURLs[outerID] = this.openedURLs[innerID] = url; 1.311 + }, 1.312 + 1.313 + onConsoleMessage: function Tester_onConsoleMessage(aConsoleMessage) { 1.314 + // Ignore empty messages. 1.315 + if (!aConsoleMessage.message) 1.316 + return; 1.317 + 1.318 + try { 1.319 + var msg = "Console message: " + aConsoleMessage.message; 1.320 + if (this.currentTest) 1.321 + this.currentTest.addResult(new testMessage(msg)); 1.322 + else 1.323 + this.dumper.dump("TEST-INFO | (browser-test.js) | " + msg.replace(/\n$/, "") + "\n"); 1.324 + } catch (ex) { 1.325 + // Swallow exception so we don't lead to another error being reported, 1.326 + // throwing us into an infinite loop 1.327 + } 1.328 + }, 1.329 + 1.330 + nextTest: Task.async(function*() { 1.331 + if (this.currentTest) { 1.332 + // Run cleanup functions for the current test before moving on to the 1.333 + // next one. 1.334 + let testScope = this.currentTest.scope; 1.335 + while (testScope.__cleanupFunctions.length > 0) { 1.336 + let func = testScope.__cleanupFunctions.shift(); 1.337 + try { 1.338 + yield func.apply(testScope); 1.339 + } 1.340 + catch (ex) { 1.341 + this.currentTest.addResult(new testResult(false, "Cleanup function threw an exception", ex, false)); 1.342 + } 1.343 + }; 1.344 + 1.345 + this.Promise.Debugging.flushUncaughtErrors(); 1.346 + 1.347 + let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor) 1.348 + .getInterface(Ci.nsIDOMWindowUtils); 1.349 + if (winUtils.isTestControllingRefreshes) { 1.350 + this.currentTest.addResult(new testResult(false, "test left refresh driver under test control", "", false)); 1.351 + winUtils.restoreNormalRefresh(); 1.352 + } 1.353 + 1.354 + if (this.SimpleTest.isExpectingUncaughtException()) { 1.355 + this.currentTest.addResult(new testResult(false, "expectUncaughtException was called but no uncaught exception was detected!", "", false)); 1.356 + } 1.357 + 1.358 + Object.keys(window).forEach(function (prop) { 1.359 + if (parseInt(prop) == prop) { 1.360 + // This is a string which when parsed as an integer and then 1.361 + // stringified gives the original string. As in, this is in fact a 1.362 + // string representation of an integer, so an index into 1.363 + // window.frames. Skip those. 1.364 + return; 1.365 + } 1.366 + if (this._globalProperties.indexOf(prop) == -1) { 1.367 + this._globalProperties.push(prop); 1.368 + if (this._globalPropertyWhitelist.indexOf(prop) == -1) 1.369 + this.currentTest.addResult(new testResult(false, "leaked window property: " + prop, "", false)); 1.370 + } 1.371 + }, this); 1.372 + 1.373 + // Clear document.popupNode. The test could have set it to a custom value 1.374 + // for its own purposes, nulling it out it will go back to the default 1.375 + // behavior of returning the last opened popup. 1.376 + document.popupNode = null; 1.377 + 1.378 + // Notify a long running test problem if it didn't end up in a timeout. 1.379 + if (this.currentTest.unexpectedTimeouts && !this.currentTest.timedOut) { 1.380 + let msg = "This test exceeded the timeout threshold. It should be " + 1.381 + "rewritten or split up. If that's not possible, use " + 1.382 + "requestLongerTimeout(N), but only as a last resort."; 1.383 + this.currentTest.addResult(new testResult(false, msg, "", false)); 1.384 + } 1.385 + 1.386 + // If we're in a debug build, check assertion counts. This code 1.387 + // is similar to the code in TestRunner.testUnloaded in 1.388 + // TestRunner.js used for all other types of mochitests. 1.389 + let debugsvc = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2); 1.390 + if (debugsvc.isDebugBuild) { 1.391 + let newAssertionCount = debugsvc.assertionCount; 1.392 + let numAsserts = newAssertionCount - this.lastAssertionCount; 1.393 + this.lastAssertionCount = newAssertionCount; 1.394 + 1.395 + let max = testScope.__expectedMaxAsserts; 1.396 + let min = testScope.__expectedMinAsserts; 1.397 + if (numAsserts > max) { 1.398 + let msg = "Assertion count " + numAsserts + 1.399 + " is greater than expected range " + 1.400 + min + "-" + max + " assertions."; 1.401 + // TEST-UNEXPECTED-FAIL (TEMPORARILY TEST-KNOWN-FAIL) 1.402 + //this.currentTest.addResult(new testResult(false, msg, "", false)); 1.403 + this.currentTest.addResult(new testResult(true, msg, "", true)); 1.404 + } else if (numAsserts < min) { 1.405 + let msg = "Assertion count " + numAsserts + 1.406 + " is less than expected range " + 1.407 + min + "-" + max + " assertions."; 1.408 + // TEST-UNEXPECTED-PASS 1.409 + this.currentTest.addResult(new testResult(false, msg, "", true)); 1.410 + } else if (numAsserts > 0) { 1.411 + let msg = "Assertion count " + numAsserts + 1.412 + " is within expected range " + 1.413 + min + "-" + max + " assertions."; 1.414 + // TEST-KNOWN-FAIL 1.415 + this.currentTest.addResult(new testResult(true, msg, "", true)); 1.416 + } 1.417 + } 1.418 + 1.419 + // Dump memory stats for main thread. 1.420 + if (Cc["@mozilla.org/xre/runtime;1"] 1.421 + .getService(Ci.nsIXULRuntime) 1.422 + .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) 1.423 + { 1.424 + this.MemoryStats.dump((l) => { this.dumper.dump(l + "\n"); }, 1.425 + this.currentTestIndex, 1.426 + this.currentTest.path, 1.427 + gConfig.dumpOutputDirectory, 1.428 + gConfig.dumpAboutMemoryAfterTest, 1.429 + gConfig.dumpDMDAfterTest); 1.430 + } 1.431 + 1.432 + // Note the test run time 1.433 + let time = Date.now() - this.lastStartTime; 1.434 + this.dumper.dump("INFO TEST-END | " + this.currentTest.path + " | finished in " + time + "ms\n"); 1.435 + this.currentTest.setDuration(time); 1.436 + 1.437 + if (this.runUntilFailure && this.currentTest.failCount > 0) { 1.438 + this.haltTests(); 1.439 + } 1.440 + 1.441 + // Restore original SimpleTest methods to avoid leaks. 1.442 + SIMPLETEST_OVERRIDES.forEach(m => { 1.443 + this.SimpleTest[m] = this.SimpleTestOriginal[m]; 1.444 + }); 1.445 + 1.446 + testScope.destroy(); 1.447 + this.currentTest.scope = null; 1.448 + } 1.449 + 1.450 + // Check the window state for the current test before moving to the next one. 1.451 + // This also causes us to check before starting any tests, since nextTest() 1.452 + // is invoked to start the tests. 1.453 + this.waitForWindowsState((function () { 1.454 + if (this.done) { 1.455 + // Uninitialize a few things explicitly so that they can clean up 1.456 + // frames and browser intentionally kept alive until shutdown to 1.457 + // eliminate false positives. 1.458 + if (gConfig.testRoot == "browser") { 1.459 + // Replace the document currently loaded in the browser's sidebar. 1.460 + // This will prevent false positives for tests that were the last 1.461 + // to touch the sidebar. They will thus not be blamed for leaking 1.462 + // a document. 1.463 + let sidebar = document.getElementById("sidebar"); 1.464 + sidebar.setAttribute("src", "data:text/html;charset=utf-8,"); 1.465 + sidebar.docShell.createAboutBlankContentViewer(null); 1.466 + sidebar.setAttribute("src", "about:blank"); 1.467 + 1.468 + // Do the same for the social sidebar. 1.469 + let socialSidebar = document.getElementById("social-sidebar-browser"); 1.470 + socialSidebar.setAttribute("src", "data:text/html;charset=utf-8,"); 1.471 + socialSidebar.docShell.createAboutBlankContentViewer(null); 1.472 + socialSidebar.setAttribute("src", "about:blank"); 1.473 + 1.474 + // Destroy BackgroundPageThumbs resources. 1.475 + let {BackgroundPageThumbs} = 1.476 + Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm", {}); 1.477 + BackgroundPageThumbs._destroy(); 1.478 + 1.479 + BrowserNewTabPreloader.uninit(); 1.480 + CustomizationTabPreloader.uninit(); 1.481 + SocialFlyout.unload(); 1.482 + SocialShare.uninit(); 1.483 + TabView.uninit(); 1.484 + } 1.485 + 1.486 + // Simulate memory pressure so that we're forced to free more resources 1.487 + // and thus get rid of more false leaks like already terminated workers. 1.488 + Services.obs.notifyObservers(null, "memory-pressure", "heap-minimize"); 1.489 + 1.490 + // Schedule GC and CC runs before finishing in order to detect 1.491 + // DOM windows leaked by our tests or the tested code. Note that we 1.492 + // use a shrinking GC so that the JS engine will discard JIT code and 1.493 + // JIT caches more aggressively. 1.494 + 1.495 + let checkForLeakedGlobalWindows = aCallback => { 1.496 + Cu.schedulePreciseShrinkingGC(() => { 1.497 + let analyzer = new CCAnalyzer(); 1.498 + analyzer.run(() => { 1.499 + let results = []; 1.500 + for (let obj of analyzer.find("nsGlobalWindow ")) { 1.501 + let m = obj.name.match(/^nsGlobalWindow #(\d+)/); 1.502 + if (m && m[1] in this.openedWindows) 1.503 + results.push({ name: obj.name, url: m[1] }); 1.504 + } 1.505 + aCallback(results); 1.506 + }); 1.507 + }); 1.508 + }; 1.509 + 1.510 + let reportLeaks = aResults => { 1.511 + for (let result of aResults) { 1.512 + let test = this.openedWindows[result.url]; 1.513 + let msg = "leaked until shutdown [" + result.name + 1.514 + " " + (this.openedURLs[result.url] || "NULL") + "]"; 1.515 + test.addResult(new testResult(false, msg, "", false)); 1.516 + } 1.517 + }; 1.518 + 1.519 + checkForLeakedGlobalWindows(aResults => { 1.520 + if (aResults.length == 0) { 1.521 + this.finish(); 1.522 + return; 1.523 + } 1.524 + // After the first check, if there are reported leaked windows, sleep 1.525 + // for a while, to allow off-main-thread work to complete and free up 1.526 + // main-thread objects. Then check again. 1.527 + setTimeout(() => { 1.528 + checkForLeakedGlobalWindows(aResults => { 1.529 + reportLeaks(aResults); 1.530 + this.finish(); 1.531 + }); 1.532 + }, 1000); 1.533 + }); 1.534 + 1.535 + return; 1.536 + } 1.537 + 1.538 + this.currentTestIndex++; 1.539 + this.execTest(); 1.540 + }).bind(this)); 1.541 + }), 1.542 + 1.543 + execTest: function Tester_execTest() { 1.544 + this.dumper.dump("TEST-START | " + this.currentTest.path + "\n"); 1.545 + 1.546 + this.SimpleTest.reset(); 1.547 + 1.548 + // Load the tests into a testscope 1.549 + let currentScope = this.currentTest.scope = new testScope(this, this.currentTest); 1.550 + let currentTest = this.currentTest; 1.551 + 1.552 + // Import utils in the test scope. 1.553 + this.currentTest.scope.EventUtils = this.EventUtils; 1.554 + this.currentTest.scope.SimpleTest = this.SimpleTest; 1.555 + this.currentTest.scope.gTestPath = this.currentTest.path; 1.556 + this.currentTest.scope.Task = this.Task; 1.557 + this.currentTest.scope.Promise = this.Promise; 1.558 + // Pass a custom report function for mochitest style reporting. 1.559 + this.currentTest.scope.Assert = new this.Assert(function(err, message, stack) { 1.560 + let res; 1.561 + if (err) { 1.562 + res = new testResult(false, err.message, err.stack, false, err.stack); 1.563 + } else { 1.564 + res = new testResult(true, message, "", false, stack); 1.565 + } 1.566 + currentTest.addResult(res); 1.567 + }); 1.568 + 1.569 + // Allow Assert.jsm methods to be tacked to the current scope. 1.570 + this.currentTest.scope.export_assertions = function() { 1.571 + for (let func in this.Assert) { 1.572 + this[func] = this.Assert[func].bind(this.Assert); 1.573 + } 1.574 + }; 1.575 + 1.576 + // Override SimpleTest methods with ours. 1.577 + SIMPLETEST_OVERRIDES.forEach(function(m) { 1.578 + this.SimpleTest[m] = this[m]; 1.579 + }, this.currentTest.scope); 1.580 + 1.581 + //load the tools to work with chrome .jar and remote 1.582 + try { 1.583 + this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", this.currentTest.scope); 1.584 + } catch (ex) { /* no chrome-harness tools */ } 1.585 + 1.586 + // Import head.js script if it exists. 1.587 + var currentTestDirPath = 1.588 + this.currentTest.path.substr(0, this.currentTest.path.lastIndexOf("/")); 1.589 + var headPath = currentTestDirPath + "/head.js"; 1.590 + try { 1.591 + this._scriptLoader.loadSubScript(headPath, this.currentTest.scope); 1.592 + } catch (ex) { 1.593 + // Ignore if no head.js exists, but report all other errors. Note this 1.594 + // will also ignore an existing head.js attempting to import a missing 1.595 + // module - see bug 755558 for why this strategy is preferred anyway. 1.596 + if (ex.toString() != 'Error opening input stream (invalid filename?)') { 1.597 + this.currentTest.addResult(new testResult(false, "head.js import threw an exception", ex, false)); 1.598 + } 1.599 + } 1.600 + 1.601 + // Import the test script. 1.602 + try { 1.603 + this._scriptLoader.loadSubScript(this.currentTest.path, 1.604 + this.currentTest.scope); 1.605 + this.Promise.Debugging.flushUncaughtErrors(); 1.606 + // Run the test 1.607 + this.lastStartTime = Date.now(); 1.608 + if (this.currentTest.scope.__tasks) { 1.609 + // This test consists of tasks, added via the `add_task()` API. 1.610 + if ("test" in this.currentTest.scope) { 1.611 + throw "Cannot run both a add_task test and a normal test at the same time."; 1.612 + } 1.613 + this.Task.spawn(function() { 1.614 + let task; 1.615 + while ((task = this.__tasks.shift())) { 1.616 + this.SimpleTest.info("Entering test " + task.name); 1.617 + try { 1.618 + yield task(); 1.619 + } catch (ex) { 1.620 + let isExpected = !!this.SimpleTest.isExpectingUncaughtException(); 1.621 + let stack = (typeof ex == "object" && "stack" in ex)?ex.stack:null; 1.622 + let name = "Uncaught exception"; 1.623 + let result = new testResult(isExpected, name, ex, false, stack); 1.624 + currentTest.addResult(result); 1.625 + } 1.626 + this.Promise.Debugging.flushUncaughtErrors(); 1.627 + this.SimpleTest.info("Leaving test " + task.name); 1.628 + } 1.629 + this.finish(); 1.630 + }.bind(currentScope)); 1.631 + } else if ("generatorTest" in this.currentTest.scope) { 1.632 + if ("test" in this.currentTest.scope) { 1.633 + throw "Cannot run both a generator test and a normal test at the same time."; 1.634 + } 1.635 + 1.636 + // This test is a generator. It will not finish immediately. 1.637 + this.currentTest.scope.waitForExplicitFinish(); 1.638 + var result = this.currentTest.scope.generatorTest(); 1.639 + this.currentTest.scope.__generator = result; 1.640 + result.next(); 1.641 + } else { 1.642 + this.currentTest.scope.test(); 1.643 + } 1.644 + } catch (ex) { 1.645 + let isExpected = !!this.SimpleTest.isExpectingUncaughtException(); 1.646 + if (!this.SimpleTest.isIgnoringAllUncaughtExceptions()) { 1.647 + this.currentTest.addResult(new testResult(isExpected, "Exception thrown", ex, false)); 1.648 + this.SimpleTest.expectUncaughtException(false); 1.649 + } else { 1.650 + this.currentTest.addResult(new testMessage("Exception thrown: " + ex)); 1.651 + } 1.652 + this.currentTest.scope.finish(); 1.653 + } 1.654 + 1.655 + // If the test ran synchronously, move to the next test, otherwise the test 1.656 + // will trigger the next test when it is done. 1.657 + if (this.currentTest.scope.__done) { 1.658 + this.nextTest(); 1.659 + } 1.660 + else { 1.661 + var self = this; 1.662 + this.currentTest.scope.__waitTimer = setTimeout(function timeoutFn() { 1.663 + if (--self.currentTest.scope.__timeoutFactor > 0) { 1.664 + // We were asked to wait a bit longer. 1.665 + self.currentTest.scope.info( 1.666 + "Longer timeout required, waiting longer... Remaining timeouts: " + 1.667 + self.currentTest.scope.__timeoutFactor); 1.668 + self.currentTest.scope.__waitTimer = 1.669 + setTimeout(timeoutFn, gTimeoutSeconds * 1000); 1.670 + return; 1.671 + } 1.672 + 1.673 + // If the test is taking longer than expected, but it's not hanging, 1.674 + // mark the fact, but let the test continue. At the end of the test, 1.675 + // if it didn't timeout, we will notify the problem through an error. 1.676 + // To figure whether it's an actual hang, compare the time of the last 1.677 + // result or message to half of the timeout time. 1.678 + // Though, to protect against infinite loops, limit the number of times 1.679 + // we allow the test to proceed. 1.680 + const MAX_UNEXPECTED_TIMEOUTS = 10; 1.681 + if (Date.now() - self.currentTest.lastOutputTime < (gTimeoutSeconds / 2) * 1000 && 1.682 + ++self.currentTest.unexpectedTimeouts <= MAX_UNEXPECTED_TIMEOUTS) { 1.683 + self.currentTest.scope.__waitTimer = 1.684 + setTimeout(timeoutFn, gTimeoutSeconds * 1000); 1.685 + return; 1.686 + } 1.687 + 1.688 + self.currentTest.addResult(new testResult(false, "Test timed out", "", false)); 1.689 + self.currentTest.timedOut = true; 1.690 + self.currentTest.scope.__waitTimer = null; 1.691 + self.nextTest(); 1.692 + }, gTimeoutSeconds * 1000); 1.693 + } 1.694 + }, 1.695 + 1.696 + QueryInterface: function(aIID) { 1.697 + if (aIID.equals(Ci.nsIConsoleListener) || 1.698 + aIID.equals(Ci.nsISupports)) 1.699 + return this; 1.700 + 1.701 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.702 + } 1.703 +}; 1.704 + 1.705 +function testResult(aCondition, aName, aDiag, aIsTodo, aStack) { 1.706 + this.msg = aName || ""; 1.707 + 1.708 + this.info = false; 1.709 + this.pass = !!aCondition; 1.710 + this.todo = aIsTodo; 1.711 + 1.712 + if (this.pass) { 1.713 + if (aIsTodo) 1.714 + this.result = "TEST-KNOWN-FAIL"; 1.715 + else 1.716 + this.result = "TEST-PASS"; 1.717 + } else { 1.718 + if (aDiag) { 1.719 + if (typeof aDiag == "object" && "fileName" in aDiag) { 1.720 + // we have an exception - print filename and linenumber information 1.721 + this.msg += " at " + aDiag.fileName + ":" + aDiag.lineNumber; 1.722 + } 1.723 + this.msg += " - " + aDiag; 1.724 + } 1.725 + if (aStack) { 1.726 + this.msg += "\nStack trace:\n"; 1.727 + var frame = aStack; 1.728 + while (frame) { 1.729 + this.msg += " " + frame + "\n"; 1.730 + frame = frame.caller; 1.731 + } 1.732 + } 1.733 + if (aIsTodo) 1.734 + this.result = "TEST-UNEXPECTED-PASS"; 1.735 + else 1.736 + this.result = "TEST-UNEXPECTED-FAIL"; 1.737 + 1.738 + if (gConfig.debugOnFailure) { 1.739 + // You've hit this line because you requested to break into the 1.740 + // debugger upon a testcase failure on your test run. 1.741 + debugger; 1.742 + } 1.743 + } 1.744 +} 1.745 + 1.746 +function testMessage(aName) { 1.747 + this.msg = aName || ""; 1.748 + this.info = true; 1.749 + this.result = "TEST-INFO"; 1.750 +} 1.751 + 1.752 +// Need to be careful adding properties to this object, since its properties 1.753 +// cannot conflict with global variables used in tests. 1.754 +function testScope(aTester, aTest) { 1.755 + this.__tester = aTester; 1.756 + 1.757 + var self = this; 1.758 + this.ok = function test_ok(condition, name, diag, stack) { 1.759 + aTest.addResult(new testResult(condition, name, diag, false, 1.760 + stack ? stack : Components.stack.caller)); 1.761 + }; 1.762 + this.is = function test_is(a, b, name) { 1.763 + self.ok(a == b, name, "Got " + a + ", expected " + b, false, 1.764 + Components.stack.caller); 1.765 + }; 1.766 + this.isnot = function test_isnot(a, b, name) { 1.767 + self.ok(a != b, name, "Didn't expect " + a + ", but got it", false, 1.768 + Components.stack.caller); 1.769 + }; 1.770 + this.ise = function test_ise(a, b, name) { 1.771 + self.ok(a === b, name, "Got " + a + ", strictly expected " + b, false, 1.772 + Components.stack.caller); 1.773 + }; 1.774 + this.todo = function test_todo(condition, name, diag, stack) { 1.775 + aTest.addResult(new testResult(!condition, name, diag, true, 1.776 + stack ? stack : Components.stack.caller)); 1.777 + }; 1.778 + this.todo_is = function test_todo_is(a, b, name) { 1.779 + self.todo(a == b, name, "Got " + a + ", expected " + b, 1.780 + Components.stack.caller); 1.781 + }; 1.782 + this.todo_isnot = function test_todo_isnot(a, b, name) { 1.783 + self.todo(a != b, name, "Didn't expect " + a + ", but got it", 1.784 + Components.stack.caller); 1.785 + }; 1.786 + this.info = function test_info(name) { 1.787 + aTest.addResult(new testMessage(name)); 1.788 + }; 1.789 + 1.790 + this.executeSoon = function test_executeSoon(func) { 1.791 + Services.tm.mainThread.dispatch({ 1.792 + run: function() { 1.793 + func(); 1.794 + } 1.795 + }, Ci.nsIThread.DISPATCH_NORMAL); 1.796 + }; 1.797 + 1.798 + this.nextStep = function test_nextStep(arg) { 1.799 + if (self.__done) { 1.800 + aTest.addResult(new testResult(false, "nextStep was called too many times", "", false)); 1.801 + return; 1.802 + } 1.803 + 1.804 + if (!self.__generator) { 1.805 + aTest.addResult(new testResult(false, "nextStep called with no generator", "", false)); 1.806 + self.finish(); 1.807 + return; 1.808 + } 1.809 + 1.810 + try { 1.811 + self.__generator.send(arg); 1.812 + } catch (ex if ex instanceof StopIteration) { 1.813 + // StopIteration means test is finished. 1.814 + self.finish(); 1.815 + } catch (ex) { 1.816 + var isExpected = !!self.SimpleTest.isExpectingUncaughtException(); 1.817 + if (!self.SimpleTest.isIgnoringAllUncaughtExceptions()) { 1.818 + aTest.addResult(new testResult(isExpected, "Exception thrown", ex, false)); 1.819 + self.SimpleTest.expectUncaughtException(false); 1.820 + } else { 1.821 + aTest.addResult(new testMessage("Exception thrown: " + ex)); 1.822 + } 1.823 + self.finish(); 1.824 + } 1.825 + }; 1.826 + 1.827 + this.waitForExplicitFinish = function test_waitForExplicitFinish() { 1.828 + self.__done = false; 1.829 + }; 1.830 + 1.831 + this.waitForFocus = function test_waitForFocus(callback, targetWindow, expectBlankPage) { 1.832 + self.SimpleTest.waitForFocus(callback, targetWindow, expectBlankPage); 1.833 + }; 1.834 + 1.835 + this.waitForClipboard = function test_waitForClipboard(expected, setup, success, failure, flavor) { 1.836 + self.SimpleTest.waitForClipboard(expected, setup, success, failure, flavor); 1.837 + }; 1.838 + 1.839 + this.registerCleanupFunction = function test_registerCleanupFunction(aFunction) { 1.840 + self.__cleanupFunctions.push(aFunction); 1.841 + }; 1.842 + 1.843 + this.requestLongerTimeout = function test_requestLongerTimeout(aFactor) { 1.844 + self.__timeoutFactor = aFactor; 1.845 + }; 1.846 + 1.847 + this.copyToProfile = function test_copyToProfile(filename) { 1.848 + self.SimpleTest.copyToProfile(filename); 1.849 + }; 1.850 + 1.851 + this.expectUncaughtException = function test_expectUncaughtException(aExpecting) { 1.852 + self.SimpleTest.expectUncaughtException(aExpecting); 1.853 + }; 1.854 + 1.855 + this.ignoreAllUncaughtExceptions = function test_ignoreAllUncaughtExceptions(aIgnoring) { 1.856 + self.SimpleTest.ignoreAllUncaughtExceptions(aIgnoring); 1.857 + }; 1.858 + 1.859 + this.expectAssertions = function test_expectAssertions(aMin, aMax) { 1.860 + let min = aMin; 1.861 + let max = aMax; 1.862 + if (typeof(max) == "undefined") { 1.863 + max = min; 1.864 + } 1.865 + if (typeof(min) != "number" || typeof(max) != "number" || 1.866 + min < 0 || max < min) { 1.867 + throw "bad parameter to expectAssertions"; 1.868 + } 1.869 + self.__expectedMinAsserts = min; 1.870 + self.__expectedMaxAsserts = max; 1.871 + }; 1.872 + 1.873 + this.finish = function test_finish() { 1.874 + self.__done = true; 1.875 + if (self.__waitTimer) { 1.876 + self.executeSoon(function() { 1.877 + if (self.__done && self.__waitTimer) { 1.878 + clearTimeout(self.__waitTimer); 1.879 + self.__waitTimer = null; 1.880 + self.__tester.nextTest(); 1.881 + } 1.882 + }); 1.883 + } 1.884 + }; 1.885 +} 1.886 +testScope.prototype = { 1.887 + __done: true, 1.888 + __generator: null, 1.889 + __tasks: null, 1.890 + __waitTimer: null, 1.891 + __cleanupFunctions: [], 1.892 + __timeoutFactor: 1, 1.893 + __expectedMinAsserts: 0, 1.894 + __expectedMaxAsserts: 0, 1.895 + 1.896 + EventUtils: {}, 1.897 + SimpleTest: {}, 1.898 + Task: null, 1.899 + Promise: null, 1.900 + Assert: null, 1.901 + 1.902 + /** 1.903 + * Add a test function which is a Task function. 1.904 + * 1.905 + * Task functions are functions fed into Task.jsm's Task.spawn(). They are 1.906 + * generators that emit promises. 1.907 + * 1.908 + * If an exception is thrown, an assertion fails, or if a rejected 1.909 + * promise is yielded, the test function aborts immediately and the test is 1.910 + * reported as a failure. Execution continues with the next test function. 1.911 + * 1.912 + * To trigger premature (but successful) termination of the function, simply 1.913 + * return or throw a Task.Result instance. 1.914 + * 1.915 + * Example usage: 1.916 + * 1.917 + * add_task(function test() { 1.918 + * let result = yield Promise.resolve(true); 1.919 + * 1.920 + * ok(result); 1.921 + * 1.922 + * let secondary = yield someFunctionThatReturnsAPromise(result); 1.923 + * is(secondary, "expected value"); 1.924 + * }); 1.925 + * 1.926 + * add_task(function test_early_return() { 1.927 + * let result = yield somethingThatReturnsAPromise(); 1.928 + * 1.929 + * if (!result) { 1.930 + * // Test is ended immediately, with success. 1.931 + * return; 1.932 + * } 1.933 + * 1.934 + * is(result, "foo"); 1.935 + * }); 1.936 + */ 1.937 + add_task: function(aFunction) { 1.938 + if (!this.__tasks) { 1.939 + this.waitForExplicitFinish(); 1.940 + this.__tasks = []; 1.941 + } 1.942 + this.__tasks.push(aFunction.bind(this)); 1.943 + }, 1.944 + 1.945 + destroy: function test_destroy() { 1.946 + for (let prop in this) 1.947 + delete this[prop]; 1.948 + } 1.949 +};