browser-test.js 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087
  1. /* -*- js-indent-level: 2; tab-width: 2; indent-tabs-mode: nil -*- */
  2. // Test timeout (seconds)
  3. var gTimeoutSeconds = 45;
  4. var gConfig;
  5. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  6. Cu.import("resource://gre/modules/Task.jsm");
  7. Cu.import("resource://gre/modules/AppConstants.jsm");
  8. Cu.import("resource://gre/modules/Services.jsm");
  9. XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
  10. "resource:///modules/ContentSearch.jsm");
  11. XPCOMUtils.defineLazyModuleGetter(this, "SelfSupportBackend",
  12. "resource:///modules/SelfSupportBackend.jsm");
  13. const SIMPLETEST_OVERRIDES =
  14. ["ok", "is", "isnot", "todo", "todo_is", "todo_isnot", "info", "expectAssertions", "requestCompleteLog"];
  15. // non-android is bootstrapped by marionette
  16. if (Services.appinfo.OS == 'Android') {
  17. window.addEventListener("load", function testOnLoad() {
  18. window.removeEventListener("load", testOnLoad);
  19. window.addEventListener("MozAfterPaint", function testOnMozAfterPaint() {
  20. window.removeEventListener("MozAfterPaint", testOnMozAfterPaint);
  21. setTimeout(testInit, 0);
  22. });
  23. });
  24. } else {
  25. setTimeout(testInit, 0);
  26. }
  27. var TabDestroyObserver = {
  28. outstanding: new Set(),
  29. promiseResolver: null,
  30. init: function() {
  31. Services.obs.addObserver(this, "message-manager-close", false);
  32. Services.obs.addObserver(this, "message-manager-disconnect", false);
  33. },
  34. destroy: function() {
  35. Services.obs.removeObserver(this, "message-manager-close");
  36. Services.obs.removeObserver(this, "message-manager-disconnect");
  37. },
  38. observe: function(subject, topic, data) {
  39. if (topic == "message-manager-close") {
  40. this.outstanding.add(subject);
  41. } else if (topic == "message-manager-disconnect") {
  42. this.outstanding.delete(subject);
  43. if (!this.outstanding.size && this.promiseResolver) {
  44. this.promiseResolver();
  45. }
  46. }
  47. },
  48. wait: function() {
  49. if (!this.outstanding.size) {
  50. return Promise.resolve();
  51. }
  52. return new Promise((resolve) => {
  53. this.promiseResolver = resolve;
  54. });
  55. },
  56. };
  57. function testInit() {
  58. gConfig = readConfig();
  59. if (gConfig.testRoot == "browser") {
  60. // Make sure to launch the test harness for the first opened window only
  61. var prefs = Services.prefs;
  62. if (prefs.prefHasUserValue("testing.browserTestHarness.running"))
  63. return;
  64. prefs.setBoolPref("testing.browserTestHarness.running", true);
  65. if (prefs.prefHasUserValue("testing.browserTestHarness.timeout"))
  66. gTimeoutSeconds = prefs.getIntPref("testing.browserTestHarness.timeout");
  67. var sstring = Cc["@mozilla.org/supports-string;1"].
  68. createInstance(Ci.nsISupportsString);
  69. sstring.data = location.search;
  70. Services.ww.openWindow(window, "chrome://mochikit/content/browser-harness.xul", "browserTest",
  71. "chrome,centerscreen,dialog=no,resizable,titlebar,toolbar=no,width=800,height=600", sstring);
  72. } else {
  73. // This code allows us to redirect without requiring specialpowers for chrome and a11y tests.
  74. let messageHandler = function(m) {
  75. messageManager.removeMessageListener("chromeEvent", messageHandler);
  76. var url = m.json.data;
  77. // Window is the [ChromeWindow] for messageManager, so we need content.window
  78. // Currently chrome tests are run in a content window instead of a ChromeWindow
  79. var webNav = content.window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
  80. .getInterface(Components.interfaces.nsIWebNavigation);
  81. webNav.loadURI(url, null, null, null, null);
  82. };
  83. 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);';
  84. messageManager.addMessageListener("chromeEvent", messageHandler);
  85. messageManager.loadFrameScript(listener, true);
  86. }
  87. if (gConfig.e10s) {
  88. e10s_init();
  89. let processCount = prefs.getIntPref("dom.ipc.processCount", 1);
  90. if (processCount > 1) {
  91. // Currently starting a content process is slow, to aviod timeouts, let's
  92. // keep alive content processes.
  93. prefs.setIntPref("dom.ipc.keepProcessesAlive", processCount);
  94. }
  95. let globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
  96. .getService(Ci.nsIMessageListenerManager);
  97. globalMM.loadFrameScript("chrome://mochikit/content/shutdown-leaks-collector.js", true);
  98. } else {
  99. // In non-e10s, only run the ShutdownLeaksCollector in the parent process.
  100. Components.utils.import("chrome://mochikit/content/ShutdownLeaksCollector.jsm");
  101. }
  102. let gmm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
  103. gmm.loadFrameScript("chrome://mochikit/content/tests/SimpleTest/AsyncUtilsContent.js", true);
  104. }
  105. function Tester(aTests, structuredLogger, aCallback) {
  106. this.structuredLogger = structuredLogger;
  107. this.tests = aTests;
  108. this.callback = aCallback;
  109. this._scriptLoader = Services.scriptloader;
  110. this.EventUtils = {};
  111. this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", this.EventUtils);
  112. var simpleTestScope = {};
  113. this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js", simpleTestScope);
  114. this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js", simpleTestScope);
  115. this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromePowers.js", simpleTestScope);
  116. this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SimpleTest.js", simpleTestScope);
  117. this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/MemoryStats.js", simpleTestScope);
  118. this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", simpleTestScope);
  119. this.SimpleTest = simpleTestScope.SimpleTest;
  120. var extensionUtilsScope = {
  121. registerCleanupFunction: (fn) => {
  122. this.currentTest.scope.registerCleanupFunction(fn);
  123. },
  124. };
  125. extensionUtilsScope.SimpleTest = this.SimpleTest;
  126. this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js", extensionUtilsScope);
  127. this.ExtensionTestUtils = extensionUtilsScope.ExtensionTestUtils;
  128. this.SimpleTest.harnessParameters = gConfig;
  129. this.MemoryStats = simpleTestScope.MemoryStats;
  130. this.Task = Task;
  131. this.ContentTask = Components.utils.import("resource://testing-common/ContentTask.jsm", null).ContentTask;
  132. this.BrowserTestUtils = Components.utils.import("resource://testing-common/BrowserTestUtils.jsm", null).BrowserTestUtils;
  133. this.TestUtils = Components.utils.import("resource://testing-common/TestUtils.jsm", null).TestUtils;
  134. this.Task.Debugging.maintainStack = true;
  135. this.Promise = Components.utils.import("resource://gre/modules/Promise.jsm", null).Promise;
  136. this.Assert = Components.utils.import("resource://testing-common/Assert.jsm", null).Assert;
  137. this.SimpleTestOriginal = {};
  138. SIMPLETEST_OVERRIDES.forEach(m => {
  139. this.SimpleTestOriginal[m] = this.SimpleTest[m];
  140. });
  141. this._coverageCollector = null;
  142. this._toleratedUncaughtRejections = null;
  143. this._uncaughtErrorObserver = function({message, date, fileName, stack, lineNumber}) {
  144. let error = message;
  145. if (fileName || lineNumber) {
  146. error = {
  147. fileName: fileName,
  148. lineNumber: lineNumber,
  149. message: message,
  150. toString: function() {
  151. return message;
  152. }
  153. };
  154. }
  155. // We may have a whitelist of rejections we wish to tolerate.
  156. let tolerate = this._toleratedUncaughtRejections &&
  157. this._toleratedUncaughtRejections.indexOf(message) != -1;
  158. let name = "A promise chain failed to handle a rejection: ";
  159. if (tolerate) {
  160. name = "WARNING: (PLEASE FIX THIS AS PART OF BUG 1077403) " + name;
  161. }
  162. this.currentTest.addResult(
  163. new testResult(
  164. /*success*/tolerate,
  165. /*name*/name,
  166. /*error*/error,
  167. /*known*/tolerate,
  168. /*stack*/stack));
  169. }.bind(this);
  170. }
  171. Tester.prototype = {
  172. EventUtils: {},
  173. SimpleTest: {},
  174. Task: null,
  175. ContentTask: null,
  176. ExtensionTestUtils: null,
  177. Assert: null,
  178. repeat: 0,
  179. runUntilFailure: false,
  180. checker: null,
  181. currentTestIndex: -1,
  182. lastStartTime: null,
  183. lastAssertionCount: 0,
  184. failuresFromInitialWindowState: 0,
  185. get currentTest() {
  186. return this.tests[this.currentTestIndex];
  187. },
  188. get done() {
  189. return this.currentTestIndex == this.tests.length - 1;
  190. },
  191. start: function Tester_start() {
  192. TabDestroyObserver.init();
  193. //if testOnLoad was not called, then gConfig is not defined
  194. if (!gConfig)
  195. gConfig = readConfig();
  196. if (gConfig.runUntilFailure)
  197. this.runUntilFailure = true;
  198. if (gConfig.repeat)
  199. this.repeat = gConfig.repeat;
  200. if (gConfig.jscovDirPrefix) {
  201. let coveragePath = gConfig.jscovDirPrefix;
  202. let {CoverageCollector} = Cu.import("resource://testing-common/CoverageUtils.jsm",
  203. {});
  204. this._coverageCollector = new CoverageCollector(coveragePath);
  205. }
  206. this.structuredLogger.info("*** Start BrowserChrome Test Results ***");
  207. Services.console.registerListener(this);
  208. this._globalProperties = Object.keys(window);
  209. this._globalPropertyWhitelist = [
  210. "navigator", "constructor", "top",
  211. "Application",
  212. "__SS_tabsToRestore", "__SSi",
  213. "webConsoleCommandController",
  214. ];
  215. this.Promise.Debugging.clearUncaughtErrorObservers();
  216. this.Promise.Debugging.addUncaughtErrorObserver(this._uncaughtErrorObserver);
  217. if (this.tests.length)
  218. this.waitForGraphicsTestWindowToBeGone(this.nextTest.bind(this));
  219. else
  220. this.finish();
  221. },
  222. waitForGraphicsTestWindowToBeGone(aCallback) {
  223. let windowsEnum = Services.wm.getEnumerator(null);
  224. while (windowsEnum.hasMoreElements()) {
  225. let win = windowsEnum.getNext();
  226. if (win != window && !win.closed &&
  227. win.document.documentURI == "chrome://gfxsanity/content/sanityparent.html") {
  228. this.BrowserTestUtils.domWindowClosed(win).then(aCallback);
  229. return;
  230. }
  231. }
  232. // graphics test window is already gone, just call callback immediately
  233. aCallback();
  234. },
  235. waitForWindowsState: function Tester_waitForWindowsState(aCallback) {
  236. let timedOut = this.currentTest && this.currentTest.timedOut;
  237. let startTime = Date.now();
  238. let baseMsg = timedOut ? "Found a {elt} after previous test timed out"
  239. : this.currentTest ? "Found an unexpected {elt} at the end of test run"
  240. : "Found an unexpected {elt}";
  241. // Remove stale tabs
  242. if (this.currentTest && window.gBrowser && gBrowser.tabs.length > 1) {
  243. while (gBrowser.tabs.length > 1) {
  244. let lastTab = gBrowser.tabContainer.lastChild;
  245. let msg = baseMsg.replace("{elt}", "tab") +
  246. ": " + lastTab.linkedBrowser.currentURI.spec;
  247. this.currentTest.addResult(new testResult(false, msg, "", false));
  248. gBrowser.removeTab(lastTab);
  249. }
  250. }
  251. // Replace the last tab with a fresh one
  252. if (window.gBrowser) {
  253. let newTab = gBrowser.addTab("about:blank", { skipAnimation: true });
  254. gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
  255. gBrowser.stop();
  256. }
  257. // Remove stale windows
  258. this.structuredLogger.info("checking window state");
  259. let windowsEnum = Services.wm.getEnumerator(null);
  260. let createdFakeTestForLogging = false;
  261. while (windowsEnum.hasMoreElements()) {
  262. let win = windowsEnum.getNext();
  263. if (win != window && !win.closed &&
  264. win.document.documentElement.getAttribute("id") != "browserTestHarness") {
  265. let type = win.document.documentElement.getAttribute("windowtype");
  266. switch (type) {
  267. case "navigator:browser":
  268. type = "browser window";
  269. break;
  270. case null:
  271. type = "unknown window with document URI: " + win.document.documentURI +
  272. " and title: " + win.document.title;
  273. break;
  274. }
  275. let msg = baseMsg.replace("{elt}", type);
  276. if (this.currentTest) {
  277. this.currentTest.addResult(new testResult(false, msg, "", false));
  278. } else {
  279. if (!createdFakeTestForLogging) {
  280. createdFakeTestForLogging = true;
  281. this.structuredLogger.testStart("browser-test.js");
  282. }
  283. this.failuresFromInitialWindowState++;
  284. this.structuredLogger.testStatus("browser-test.js",
  285. msg, "FAIL", false, "");
  286. }
  287. win.close();
  288. }
  289. }
  290. if (createdFakeTestForLogging) {
  291. let time = Date.now() - startTime;
  292. this.structuredLogger.testEnd("browser-test.js",
  293. "OK",
  294. undefined,
  295. "finished window state check in " + time + "ms");
  296. }
  297. // Make sure the window is raised before each test.
  298. this.SimpleTest.waitForFocus(aCallback);
  299. },
  300. finish: function Tester_finish(aSkipSummary) {
  301. this.Promise.Debugging.flushUncaughtErrors();
  302. var passCount = this.tests.reduce((a, f) => a + f.passCount, 0);
  303. var failCount = this.tests.reduce((a, f) => a + f.failCount, 0);
  304. var todoCount = this.tests.reduce((a, f) => a + f.todoCount, 0);
  305. // Include failures from window state checking prior to running the first test
  306. failCount += this.failuresFromInitialWindowState;
  307. if (this.repeat > 0) {
  308. --this.repeat;
  309. this.currentTestIndex = -1;
  310. this.nextTest();
  311. } else {
  312. TabDestroyObserver.destroy();
  313. Services.console.unregisterListener(this);
  314. this.Promise.Debugging.clearUncaughtErrorObservers();
  315. this._treatUncaughtRejectionsAsFailures = false;
  316. // In the main process, we print the ShutdownLeaksCollector message here.
  317. let pid = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processID;
  318. dump("Completed ShutdownLeaks collections in process " + pid + "\n");
  319. this.structuredLogger.info("TEST-START | Shutdown");
  320. if (this.tests.length) {
  321. let e10sMode = gMultiProcessBrowser ? "e10s" : "non-e10s";
  322. this.structuredLogger.info("Browser Chrome Test Summary");
  323. this.structuredLogger.info("Passed: " + passCount);
  324. this.structuredLogger.info("Failed: " + failCount);
  325. this.structuredLogger.info("Todo: " + todoCount);
  326. this.structuredLogger.info("Mode: " + e10sMode);
  327. } else {
  328. this.structuredLogger.testEnd("browser-test.js",
  329. "FAIL",
  330. "PASS",
  331. "No tests to run. Did you pass invalid test_paths?");
  332. }
  333. this.structuredLogger.info("*** End BrowserChrome Test Results ***");
  334. // Tests complete, notify the callback and return
  335. this.callback(this.tests);
  336. this.callback = null;
  337. this.tests = null;
  338. }
  339. },
  340. haltTests: function Tester_haltTests() {
  341. // Do not run any further tests
  342. this.currentTestIndex = this.tests.length - 1;
  343. this.repeat = 0;
  344. },
  345. observe: function Tester_observe(aSubject, aTopic, aData) {
  346. if (!aTopic) {
  347. this.onConsoleMessage(aSubject);
  348. }
  349. },
  350. onConsoleMessage: function Tester_onConsoleMessage(aConsoleMessage) {
  351. // Ignore empty messages.
  352. if (!aConsoleMessage.message)
  353. return;
  354. try {
  355. var msg = "Console message: " + aConsoleMessage.message;
  356. if (this.currentTest)
  357. this.currentTest.addResult(new testMessage(msg));
  358. else
  359. this.structuredLogger.info("TEST-INFO | (browser-test.js) | " + msg.replace(/\n$/, "") + "\n");
  360. } catch (ex) {
  361. // Swallow exception so we don't lead to another error being reported,
  362. // throwing us into an infinite loop
  363. }
  364. },
  365. nextTest: Task.async(function*() {
  366. if (this.currentTest) {
  367. this.Promise.Debugging.flushUncaughtErrors();
  368. if (this._coverageCollector) {
  369. this._coverageCollector.recordTestCoverage(this.currentTest.path);
  370. }
  371. // Run cleanup functions for the current test before moving on to the
  372. // next one.
  373. let testScope = this.currentTest.scope;
  374. while (testScope.__cleanupFunctions.length > 0) {
  375. let func = testScope.__cleanupFunctions.shift();
  376. try {
  377. yield func.apply(testScope);
  378. }
  379. catch (ex) {
  380. this.currentTest.addResult(new testResult(false, "Cleanup function threw an exception", ex, false));
  381. }
  382. }
  383. if (this.currentTest.passCount === 0 &&
  384. this.currentTest.failCount === 0 &&
  385. this.currentTest.todoCount === 0) {
  386. this.currentTest.addResult(new testResult(false, "This test contains no passes, no fails and no todos. Maybe it threw a silent exception? Make sure you use waitForExplicitFinish() if you need it.", "", false));
  387. }
  388. if (testScope.__expected == 'fail' && testScope.__num_failed <= 0) {
  389. this.currentTest.addResult(new testResult(false, "We expected at least one assertion to fail because this test file was marked as fail-if in the manifest!", "", true));
  390. }
  391. this.Promise.Debugging.flushUncaughtErrors();
  392. let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
  393. .getInterface(Ci.nsIDOMWindowUtils);
  394. if (winUtils.isTestControllingRefreshes) {
  395. this.currentTest.addResult(new testResult(false, "test left refresh driver under test control", "", false));
  396. winUtils.restoreNormalRefresh();
  397. }
  398. if (this.SimpleTest.isExpectingUncaughtException()) {
  399. this.currentTest.addResult(new testResult(false, "expectUncaughtException was called but no uncaught exception was detected!", "", false));
  400. }
  401. Object.keys(window).forEach(function (prop) {
  402. if (parseInt(prop) == prop) {
  403. // This is a string which when parsed as an integer and then
  404. // stringified gives the original string. As in, this is in fact a
  405. // string representation of an integer, so an index into
  406. // window.frames. Skip those.
  407. return;
  408. }
  409. if (this._globalProperties.indexOf(prop) == -1) {
  410. this._globalProperties.push(prop);
  411. if (this._globalPropertyWhitelist.indexOf(prop) == -1)
  412. this.currentTest.addResult(new testResult(false, "test left unexpected property on window: " + prop, "", false));
  413. }
  414. }, this);
  415. // Clear document.popupNode. The test could have set it to a custom value
  416. // for its own purposes, nulling it out it will go back to the default
  417. // behavior of returning the last opened popup.
  418. document.popupNode = null;
  419. yield new Promise(resolve => SpecialPowers.flushPrefEnv(resolve));
  420. // Notify a long running test problem if it didn't end up in a timeout.
  421. if (this.currentTest.unexpectedTimeouts && !this.currentTest.timedOut) {
  422. let msg = "This test exceeded the timeout threshold. It should be " +
  423. "rewritten or split up. If that's not possible, use " +
  424. "requestLongerTimeout(N), but only as a last resort.";
  425. this.currentTest.addResult(new testResult(false, msg, "", false));
  426. }
  427. // If we're in a debug build, check assertion counts. This code
  428. // is similar to the code in TestRunner.testUnloaded in
  429. // TestRunner.js used for all other types of mochitests.
  430. let debugsvc = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
  431. if (debugsvc.isDebugBuild) {
  432. let newAssertionCount = debugsvc.assertionCount;
  433. let numAsserts = newAssertionCount - this.lastAssertionCount;
  434. this.lastAssertionCount = newAssertionCount;
  435. let max = testScope.__expectedMaxAsserts;
  436. let min = testScope.__expectedMinAsserts;
  437. if (numAsserts > max) {
  438. let msg = "Assertion count " + numAsserts +
  439. " is greater than expected range " +
  440. min + "-" + max + " assertions.";
  441. // TEST-UNEXPECTED-FAIL (TEMPORARILY TEST-KNOWN-FAIL)
  442. //this.currentTest.addResult(new testResult(false, msg, "", false));
  443. this.currentTest.addResult(new testResult(true, msg, "", true));
  444. } else if (numAsserts < min) {
  445. let msg = "Assertion count " + numAsserts +
  446. " is less than expected range " +
  447. min + "-" + max + " assertions.";
  448. // TEST-UNEXPECTED-PASS
  449. this.currentTest.addResult(new testResult(false, msg, "", true));
  450. } else if (numAsserts > 0) {
  451. let msg = "Assertion count " + numAsserts +
  452. " is within expected range " +
  453. min + "-" + max + " assertions.";
  454. // TEST-KNOWN-FAIL
  455. this.currentTest.addResult(new testResult(true, msg, "", true));
  456. }
  457. }
  458. // Dump memory stats for main thread.
  459. if (Cc["@mozilla.org/xre/runtime;1"]
  460. .getService(Ci.nsIXULRuntime)
  461. .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
  462. {
  463. this.MemoryStats.dump(this.currentTestIndex,
  464. this.currentTest.path,
  465. gConfig.dumpOutputDirectory,
  466. gConfig.dumpAboutMemoryAfterTest,
  467. gConfig.dumpDMDAfterTest);
  468. }
  469. // Note the test run time
  470. let time = Date.now() - this.lastStartTime;
  471. this.structuredLogger.testEnd(this.currentTest.path,
  472. "OK",
  473. undefined,
  474. "finished in " + time + "ms");
  475. this.currentTest.setDuration(time);
  476. if (this.runUntilFailure && this.currentTest.failCount > 0) {
  477. this.haltTests();
  478. }
  479. // Restore original SimpleTest methods to avoid leaks.
  480. SIMPLETEST_OVERRIDES.forEach(m => {
  481. this.SimpleTest[m] = this.SimpleTestOriginal[m];
  482. });
  483. this.ContentTask.setTestScope(null);
  484. testScope.destroy();
  485. this.currentTest.scope = null;
  486. }
  487. // Check the window state for the current test before moving to the next one.
  488. // This also causes us to check before starting any tests, since nextTest()
  489. // is invoked to start the tests.
  490. this.waitForWindowsState((function () {
  491. if (this.done) {
  492. if (this._coverageCollector) {
  493. this._coverageCollector.finalize();
  494. }
  495. // Uninitialize a few things explicitly so that they can clean up
  496. // frames and browser intentionally kept alive until shutdown to
  497. // eliminate false positives.
  498. if (gConfig.testRoot == "browser") {
  499. //Skip if SeaMonkey
  500. if (AppConstants.MOZ_APP_NAME != "seamonkey") {
  501. // Replace the document currently loaded in the browser's sidebar.
  502. // This will prevent false positives for tests that were the last
  503. // to touch the sidebar. They will thus not be blamed for leaking
  504. // a document.
  505. let sidebar = document.getElementById("sidebar");
  506. sidebar.setAttribute("src", "data:text/html;charset=utf-8,");
  507. sidebar.docShell.createAboutBlankContentViewer(null);
  508. sidebar.setAttribute("src", "about:blank");
  509. SelfSupportBackend.uninit();
  510. }
  511. // Destroy BackgroundPageThumbs resources.
  512. let {BackgroundPageThumbs} =
  513. Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm", {});
  514. BackgroundPageThumbs._destroy();
  515. // Destroy preloaded browsers.
  516. if (gBrowser._preloadedBrowser) {
  517. let browser = gBrowser._preloadedBrowser;
  518. gBrowser._preloadedBrowser = null;
  519. gBrowser.getNotificationBox(browser).remove();
  520. }
  521. }
  522. // Schedule GC and CC runs before finishing in order to detect
  523. // DOM windows leaked by our tests or the tested code. Note that we
  524. // use a shrinking GC so that the JS engine will discard JIT code and
  525. // JIT caches more aggressively.
  526. let shutdownCleanup = aCallback => {
  527. Cu.schedulePreciseShrinkingGC(() => {
  528. // Run the GC and CC a few times to make sure that as much
  529. // as possible is freed.
  530. let numCycles = 3;
  531. for (let i = 0; i < numCycles; i++) {
  532. Cu.forceGC();
  533. Cu.forceCC();
  534. }
  535. aCallback();
  536. });
  537. };
  538. let {AsyncShutdown} =
  539. Cu.import("resource://gre/modules/AsyncShutdown.jsm", {});
  540. let barrier = new AsyncShutdown.Barrier(
  541. "ShutdownLeaks: Wait for cleanup to be finished before checking for leaks");
  542. Services.obs.notifyObservers({wrappedJSObject: barrier},
  543. "shutdown-leaks-before-check", null);
  544. barrier.client.addBlocker("ShutdownLeaks: Wait for tabs to finish closing",
  545. TabDestroyObserver.wait());
  546. barrier.wait().then(() => {
  547. // Simulate memory pressure so that we're forced to free more resources
  548. // and thus get rid of more false leaks like already terminated workers.
  549. Services.obs.notifyObservers(null, "memory-pressure", "heap-minimize");
  550. Services.ppmm.broadcastAsyncMessage("browser-test:collect-request");
  551. shutdownCleanup(() => {
  552. setTimeout(() => {
  553. shutdownCleanup(() => {
  554. this.finish();
  555. });
  556. }, 1000);
  557. });
  558. });
  559. return;
  560. }
  561. this.currentTestIndex++;
  562. this.execTest();
  563. }).bind(this));
  564. }),
  565. execTest: function Tester_execTest() {
  566. this.structuredLogger.testStart(this.currentTest.path);
  567. this.SimpleTest.reset();
  568. // Load the tests into a testscope
  569. let currentScope = this.currentTest.scope = new testScope(this, this.currentTest, this.currentTest.expected);
  570. let currentTest = this.currentTest;
  571. // Import utils in the test scope.
  572. this.currentTest.scope.EventUtils = this.EventUtils;
  573. this.currentTest.scope.SimpleTest = this.SimpleTest;
  574. this.currentTest.scope.gTestPath = this.currentTest.path;
  575. this.currentTest.scope.Task = this.Task;
  576. this.currentTest.scope.ContentTask = this.ContentTask;
  577. this.currentTest.scope.BrowserTestUtils = this.BrowserTestUtils;
  578. this.currentTest.scope.TestUtils = this.TestUtils;
  579. this.currentTest.scope.ExtensionTestUtils = this.ExtensionTestUtils;
  580. // Pass a custom report function for mochitest style reporting.
  581. this.currentTest.scope.Assert = new this.Assert(function(err, message, stack) {
  582. let res;
  583. if (err) {
  584. res = new testResult(false, err.message, err.stack, false, err.stack);
  585. } else {
  586. res = new testResult(true, message, "", false, stack);
  587. }
  588. currentTest.addResult(res);
  589. });
  590. this.ContentTask.setTestScope(currentScope);
  591. // Allow Assert.jsm methods to be tacked to the current scope.
  592. this.currentTest.scope.export_assertions = function() {
  593. for (let func in this.Assert) {
  594. this[func] = this.Assert[func].bind(this.Assert);
  595. }
  596. };
  597. // Override SimpleTest methods with ours.
  598. SIMPLETEST_OVERRIDES.forEach(function(m) {
  599. this.SimpleTest[m] = this[m];
  600. }, this.currentTest.scope);
  601. //load the tools to work with chrome .jar and remote
  602. try {
  603. this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", this.currentTest.scope);
  604. } catch (ex) { /* no chrome-harness tools */ }
  605. // Import head.js script if it exists.
  606. var currentTestDirPath =
  607. this.currentTest.path.substr(0, this.currentTest.path.lastIndexOf("/"));
  608. var headPath = currentTestDirPath + "/head.js";
  609. try {
  610. this._scriptLoader.loadSubScript(headPath, this.currentTest.scope);
  611. } catch (ex) {
  612. // Ignore if no head.js exists, but report all other errors. Note this
  613. // will also ignore an existing head.js attempting to import a missing
  614. // module - see bug 755558 for why this strategy is preferred anyway.
  615. if (!/^Error opening input stream/.test(ex.toString())) {
  616. this.currentTest.addResult(new testResult(false, "head.js import threw an exception", ex, false));
  617. }
  618. }
  619. // Import the test script.
  620. try {
  621. this._scriptLoader.loadSubScript(this.currentTest.path,
  622. this.currentTest.scope);
  623. this.Promise.Debugging.flushUncaughtErrors();
  624. // Run the test
  625. this.lastStartTime = Date.now();
  626. if (this.currentTest.scope.__tasks) {
  627. // This test consists of tasks, added via the `add_task()` API.
  628. if ("test" in this.currentTest.scope) {
  629. throw "Cannot run both a add_task test and a normal test at the same time.";
  630. }
  631. let Promise = this.Promise;
  632. this.Task.spawn(function*() {
  633. let task;
  634. while ((task = this.__tasks.shift())) {
  635. this.SimpleTest.info("Entering test " + task.name);
  636. try {
  637. yield task();
  638. } catch (ex) {
  639. let isExpected = !!this.SimpleTest.isExpectingUncaughtException();
  640. let stack = (typeof ex == "object" && "stack" in ex)?ex.stack:null;
  641. let name = "Uncaught exception";
  642. let result = new testResult(isExpected, name, ex, false, stack);
  643. currentTest.addResult(result);
  644. }
  645. Promise.Debugging.flushUncaughtErrors();
  646. this.SimpleTest.info("Leaving test " + task.name);
  647. }
  648. this.finish();
  649. }.bind(currentScope));
  650. } else if (typeof this.currentTest.scope.test == "function") {
  651. this.currentTest.scope.test();
  652. } else {
  653. throw "This test didn't call add_task, nor did it define a generatorTest() function, nor did it define a test() function, so we don't know how to run it.";
  654. }
  655. } catch (ex) {
  656. let isExpected = !!this.SimpleTest.isExpectingUncaughtException();
  657. if (!this.SimpleTest.isIgnoringAllUncaughtExceptions()) {
  658. this.currentTest.addResult(new testResult(isExpected, "Exception thrown", ex, false));
  659. this.SimpleTest.expectUncaughtException(false);
  660. } else {
  661. this.currentTest.addResult(new testMessage("Exception thrown: " + ex));
  662. }
  663. this.currentTest.scope.finish();
  664. }
  665. // If the test ran synchronously, move to the next test, otherwise the test
  666. // will trigger the next test when it is done.
  667. if (this.currentTest.scope.__done) {
  668. this.nextTest();
  669. }
  670. else {
  671. var self = this;
  672. var timeoutExpires = Date.now() + gTimeoutSeconds * 1000;
  673. var waitUntilAtLeast = timeoutExpires - 1000;
  674. this.currentTest.scope.__waitTimer =
  675. this.SimpleTest._originalSetTimeout.apply(window, [function timeoutFn() {
  676. // We sometimes get woken up long before the gTimeoutSeconds
  677. // have elapsed (when running in chaos mode for example). This
  678. // code ensures that we don't wrongly time out in that case.
  679. if (Date.now() < waitUntilAtLeast) {
  680. self.currentTest.scope.__waitTimer =
  681. setTimeout(timeoutFn, timeoutExpires - Date.now());
  682. return;
  683. }
  684. if (--self.currentTest.scope.__timeoutFactor > 0) {
  685. // We were asked to wait a bit longer.
  686. self.currentTest.scope.info(
  687. "Longer timeout required, waiting longer... Remaining timeouts: " +
  688. self.currentTest.scope.__timeoutFactor);
  689. self.currentTest.scope.__waitTimer =
  690. setTimeout(timeoutFn, gTimeoutSeconds * 1000);
  691. return;
  692. }
  693. // If the test is taking longer than expected, but it's not hanging,
  694. // mark the fact, but let the test continue. At the end of the test,
  695. // if it didn't timeout, we will notify the problem through an error.
  696. // To figure whether it's an actual hang, compare the time of the last
  697. // result or message to half of the timeout time.
  698. // Though, to protect against infinite loops, limit the number of times
  699. // we allow the test to proceed.
  700. const MAX_UNEXPECTED_TIMEOUTS = 10;
  701. if (Date.now() - self.currentTest.lastOutputTime < (gTimeoutSeconds / 2) * 1000 &&
  702. ++self.currentTest.unexpectedTimeouts <= MAX_UNEXPECTED_TIMEOUTS) {
  703. self.currentTest.scope.__waitTimer =
  704. setTimeout(timeoutFn, gTimeoutSeconds * 1000);
  705. return;
  706. }
  707. self.currentTest.addResult(new testResult(false, "Test timed out", null, false));
  708. self.currentTest.timedOut = true;
  709. self.currentTest.scope.__waitTimer = null;
  710. self.nextTest();
  711. }, gTimeoutSeconds * 1000]);
  712. }
  713. },
  714. QueryInterface: function(aIID) {
  715. if (aIID.equals(Ci.nsIConsoleListener) ||
  716. aIID.equals(Ci.nsISupports))
  717. return this;
  718. throw Components.results.NS_ERROR_NO_INTERFACE;
  719. }
  720. };
  721. function testResult(aCondition, aName, aDiag, aIsTodo, aStack) {
  722. this.name = aName;
  723. this.msg = "";
  724. this.info = false;
  725. this.pass = !!aCondition;
  726. this.todo = aIsTodo;
  727. if (this.pass) {
  728. if (aIsTodo) {
  729. this.status = "FAIL";
  730. this.expected = "FAIL";
  731. } else {
  732. this.status = "PASS";
  733. this.expected = "PASS";
  734. }
  735. } else {
  736. if (aDiag) {
  737. if (typeof aDiag == "object" && "fileName" in aDiag) {
  738. // we have an exception - print filename and linenumber information
  739. this.msg += "at " + aDiag.fileName + ":" + aDiag.lineNumber + " - ";
  740. }
  741. this.msg += String(aDiag);
  742. }
  743. if (aStack) {
  744. this.msg += "\nStack trace:\n";
  745. let normalized;
  746. if (aStack instanceof Components.interfaces.nsIStackFrame) {
  747. let frames = [];
  748. for (let frame = aStack; frame; frame = frame.caller) {
  749. frames.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
  750. }
  751. normalized = frames.join("\n");
  752. } else {
  753. normalized = "" + aStack;
  754. }
  755. this.msg += Task.Debugging.generateReadableStack(normalized, " ");
  756. }
  757. if (aIsTodo) {
  758. this.status = "PASS";
  759. this.expected = "FAIL";
  760. } else {
  761. this.status = "FAIL";
  762. this.expected = "PASS";
  763. }
  764. if (gConfig.debugOnFailure) {
  765. // You've hit this line because you requested to break into the
  766. // debugger upon a testcase failure on your test run.
  767. debugger;
  768. }
  769. }
  770. }
  771. function testMessage(aName) {
  772. this.msg = aName || "";
  773. this.info = true;
  774. }
  775. // Need to be careful adding properties to this object, since its properties
  776. // cannot conflict with global variables used in tests.
  777. function testScope(aTester, aTest, expected) {
  778. this.__tester = aTester;
  779. this.__expected = expected;
  780. this.__num_failed = 0;
  781. var self = this;
  782. this.ok = function test_ok(condition, name, diag, stack) {
  783. if (self.__expected == 'fail') {
  784. if (!condition) {
  785. self.__num_failed++;
  786. condition = true;
  787. }
  788. }
  789. aTest.addResult(new testResult(condition, name, diag, false,
  790. stack ? stack : Components.stack.caller));
  791. };
  792. this.is = function test_is(a, b, name) {
  793. self.ok(a == b, name, "Got " + a + ", expected " + b, false,
  794. Components.stack.caller);
  795. };
  796. this.isnot = function test_isnot(a, b, name) {
  797. self.ok(a != b, name, "Didn't expect " + a + ", but got it", false,
  798. Components.stack.caller);
  799. };
  800. this.todo = function test_todo(condition, name, diag, stack) {
  801. aTest.addResult(new testResult(!condition, name, diag, true,
  802. stack ? stack : Components.stack.caller));
  803. };
  804. this.todo_is = function test_todo_is(a, b, name) {
  805. self.todo(a == b, name, "Got " + a + ", expected " + b,
  806. Components.stack.caller);
  807. };
  808. this.todo_isnot = function test_todo_isnot(a, b, name) {
  809. self.todo(a != b, name, "Didn't expect " + a + ", but got it",
  810. Components.stack.caller);
  811. };
  812. this.info = function test_info(name) {
  813. aTest.addResult(new testMessage(name));
  814. };
  815. this.executeSoon = function test_executeSoon(func) {
  816. Services.tm.mainThread.dispatch({
  817. run: function() {
  818. func();
  819. }
  820. }, Ci.nsIThread.DISPATCH_NORMAL);
  821. };
  822. this.waitForExplicitFinish = function test_waitForExplicitFinish() {
  823. self.__done = false;
  824. };
  825. this.waitForFocus = function test_waitForFocus(callback, targetWindow, expectBlankPage) {
  826. self.SimpleTest.waitForFocus(callback, targetWindow, expectBlankPage);
  827. };
  828. this.waitForClipboard = function test_waitForClipboard(expected, setup, success, failure, flavor) {
  829. self.SimpleTest.waitForClipboard(expected, setup, success, failure, flavor);
  830. };
  831. this.registerCleanupFunction = function test_registerCleanupFunction(aFunction) {
  832. self.__cleanupFunctions.push(aFunction);
  833. };
  834. this.requestLongerTimeout = function test_requestLongerTimeout(aFactor) {
  835. self.__timeoutFactor = aFactor;
  836. };
  837. this.copyToProfile = function test_copyToProfile(filename) {
  838. self.SimpleTest.copyToProfile(filename);
  839. };
  840. this.expectUncaughtException = function test_expectUncaughtException(aExpecting) {
  841. self.SimpleTest.expectUncaughtException(aExpecting);
  842. };
  843. this.ignoreAllUncaughtExceptions = function test_ignoreAllUncaughtExceptions(aIgnoring) {
  844. self.SimpleTest.ignoreAllUncaughtExceptions(aIgnoring);
  845. };
  846. this.thisTestLeaksUncaughtRejectionsAndShouldBeFixed = function(...rejections) {
  847. if (!aTester._toleratedUncaughtRejections) {
  848. aTester._toleratedUncaughtRejections = [];
  849. }
  850. aTester._toleratedUncaughtRejections.push(...rejections);
  851. };
  852. this.expectAssertions = function test_expectAssertions(aMin, aMax) {
  853. let min = aMin;
  854. let max = aMax;
  855. if (typeof(max) == "undefined") {
  856. max = min;
  857. }
  858. if (typeof(min) != "number" || typeof(max) != "number" ||
  859. min < 0 || max < min) {
  860. throw "bad parameter to expectAssertions";
  861. }
  862. self.__expectedMinAsserts = min;
  863. self.__expectedMaxAsserts = max;
  864. };
  865. this.setExpected = function test_setExpected() {
  866. self.__expected = 'fail';
  867. };
  868. this.finish = function test_finish() {
  869. self.__done = true;
  870. if (self.__waitTimer) {
  871. self.executeSoon(function() {
  872. if (self.__done && self.__waitTimer) {
  873. clearTimeout(self.__waitTimer);
  874. self.__waitTimer = null;
  875. self.__tester.nextTest();
  876. }
  877. });
  878. }
  879. };
  880. this.requestCompleteLog = function test_requestCompleteLog() {
  881. self.__tester.structuredLogger.deactivateBuffering();
  882. self.registerCleanupFunction(function() {
  883. self.__tester.structuredLogger.activateBuffering();
  884. })
  885. };
  886. }
  887. testScope.prototype = {
  888. __done: true,
  889. __tasks: null,
  890. __waitTimer: null,
  891. __cleanupFunctions: [],
  892. __timeoutFactor: 1,
  893. __expectedMinAsserts: 0,
  894. __expectedMaxAsserts: 0,
  895. __expected: 'pass',
  896. EventUtils: {},
  897. SimpleTest: {},
  898. Task: null,
  899. ContentTask: null,
  900. BrowserTestUtils: null,
  901. TestUtils: null,
  902. ExtensionTestUtils: null,
  903. Assert: null,
  904. /**
  905. * Add a test function which is a Task function.
  906. *
  907. * Task functions are functions fed into Task.jsm's Task.spawn(). They are
  908. * generators that emit promises.
  909. *
  910. * If an exception is thrown, an assertion fails, or if a rejected
  911. * promise is yielded, the test function aborts immediately and the test is
  912. * reported as a failure. Execution continues with the next test function.
  913. *
  914. * To trigger premature (but successful) termination of the function, simply
  915. * return or throw a Task.Result instance.
  916. *
  917. * Example usage:
  918. *
  919. * add_task(function test() {
  920. * let result = yield Promise.resolve(true);
  921. *
  922. * ok(result);
  923. *
  924. * let secondary = yield someFunctionThatReturnsAPromise(result);
  925. * is(secondary, "expected value");
  926. * });
  927. *
  928. * add_task(function test_early_return() {
  929. * let result = yield somethingThatReturnsAPromise();
  930. *
  931. * if (!result) {
  932. * // Test is ended immediately, with success.
  933. * return;
  934. * }
  935. *
  936. * is(result, "foo");
  937. * });
  938. */
  939. add_task: function(aFunction) {
  940. if (!this.__tasks) {
  941. this.waitForExplicitFinish();
  942. this.__tasks = [];
  943. }
  944. this.__tasks.push(aFunction.bind(this));
  945. },
  946. destroy: function test_destroy() {
  947. for (let prop in this)
  948. delete this[prop];
  949. }
  950. };