Bug 1866777 - Disable test_race_cache_with_network.js on windows opt for frequent...
[gecko.git] / testing / xpcshell / head.js
blobc046274f4770be6cb622547bc183e730ebb14d64
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8  * This file contains common code that is loaded before each test file(s).
9  * See https://developer.mozilla.org/en-US/docs/Mozilla/QA/Writing_xpcshell-based_unit_tests
10  * for more information.
11  */
13 /* defined by the harness */
14 /* globals _HEAD_FILES, _HEAD_JS_PATH, _JSDEBUGGER_PORT, _JSCOV_DIR,
15     _MOZINFO_JS_PATH, _TEST_FILE, _TEST_NAME, _TEST_CWD, _TESTING_MODULES_DIR:true,
16     _PREFS_FILE */
18 /* defined by XPCShellImpl.cpp */
19 /* globals load, sendCommand, changeTestShellDir */
21 /* must be defined by tests using do_await_remote_message/do_send_remote_message */
22 /* globals Cc, Ci */
24 /* defined by this file but is defined as read-only for tests */
25 // eslint-disable-next-line no-redeclare
26 /* globals runningInParent: true */
28 /* may be defined in test files */
29 /* globals run_test */
31 var _quit = false;
32 var _passed = true;
33 var _tests_pending = 0;
34 var _cleanupFunctions = [];
35 var _pendingTimers = [];
36 var _profileInitialized = false;
38 // Assigned in do_load_child_test_harness.
39 var _XPCSHELL_PROCESS;
41 // Register the testing-common resource protocol early, to have access to its
42 // modules.
43 let _Services = Services;
44 _register_modules_protocol_handler();
46 let { AppConstants: _AppConstants } = ChromeUtils.importESModule(
47   "resource://gre/modules/AppConstants.sys.mjs"
50 let { PromiseTestUtils: _PromiseTestUtils } = ChromeUtils.importESModule(
51   "resource://testing-common/PromiseTestUtils.sys.mjs"
54 let { NetUtil: _NetUtil } = ChromeUtils.importESModule(
55   "resource://gre/modules/NetUtil.sys.mjs"
58 let { XPCOMUtils: _XPCOMUtils } = ChromeUtils.importESModule(
59   "resource://gre/modules/XPCOMUtils.sys.mjs"
62 // Support a common assertion library, Assert.sys.mjs.
63 var { Assert: AssertCls } = ChromeUtils.importESModule(
64   "resource://testing-common/Assert.sys.mjs"
67 // Pass a custom report function for xpcshell-test style reporting.
68 var Assert = new AssertCls(function (err, message, stack) {
69   if (err) {
70     do_report_result(false, err.message, err.stack);
71   } else {
72     do_report_result(true, message, stack);
73   }
74 }, true);
76 // Bug 1506134 for followup.  Some xpcshell tests use ContentTask.sys.mjs, which
77 // expects browser-test.js to have set a testScope that includes record.
78 function record(condition, name, diag, stack) {
79   do_report_result(condition, name, stack);
82 var _add_params = function (params) {
83   if (typeof _XPCSHELL_PROCESS != "undefined") {
84     params.xpcshell_process = _XPCSHELL_PROCESS;
85   }
88 var _dumpLog = function (raw_msg) {
89   dump("\n" + JSON.stringify(raw_msg) + "\n");
92 var { StructuredLogger: _LoggerClass } = ChromeUtils.importESModule(
93   "resource://testing-common/StructuredLog.sys.mjs"
95 var _testLogger = new _LoggerClass("xpcshell/head.js", _dumpLog, [_add_params]);
97 // Disable automatic network detection, so tests work correctly when
98 // not connected to a network.
99 _Services.io.manageOfflineStatus = false;
100 _Services.io.offline = false;
102 // Determine if we're running on parent or child
103 var runningInParent = true;
104 try {
105   // Don't use Services.appinfo here as it disables replacing appinfo with stubs
106   // for test usage.
107   runningInParent =
108     // eslint-disable-next-line mozilla/use-services
109     Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime).processType ==
110     Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
111 } catch (e) {}
113 // Only if building of places is enabled.
114 if (runningInParent && "mozIAsyncHistory" in Ci) {
115   // Ensure places history is enabled for xpcshell-tests as some non-FF
116   // apps disable it.
117   _Services.prefs.setBoolPref("places.history.enabled", true);
120 // Configure crash reporting, if possible
121 // We rely on the Python harness to set MOZ_CRASHREPORTER,
122 // MOZ_CRASHREPORTER_NO_REPORT, and handle checking for minidumps.
123 // Note that if we're in a child process, we don't want to init the
124 // crashreporter component.
125 try {
126   if (runningInParent && "@mozilla.org/toolkit/crash-reporter;1" in Cc) {
127     // Intentially access the crash reporter service directly for this.
128     // eslint-disable-next-line mozilla/use-services
129     let crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService(
130       Ci.nsICrashReporter
131     );
132     crashReporter.UpdateCrashEventsDir();
133     crashReporter.minidumpPath = do_get_minidumpdir();
135     // Tell the test harness that the crash reporter is set up, and any crash
136     // after this point is supposed to be caught by the crash reporter.
137     //
138     // Due to the limitation on the remote xpcshell test, the process return
139     // code does not represent the process crash. Any crash before this point
140     // can be caught by the absence of this log.
141     _testLogger.logData("crash_reporter_init");
142   }
143 } catch (e) {}
145 if (runningInParent) {
146   _Services.prefs.setBoolPref("dom.push.connection.enabled", false);
149 // Configure a console listener so messages sent to it are logged as part
150 // of the test.
151 try {
152   let levelNames = {};
153   for (let level of ["debug", "info", "warn", "error"]) {
154     levelNames[Ci.nsIConsoleMessage[level]] = level;
155   }
157   let listener = {
158     QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]),
159     observe(msg) {
160       if (typeof info === "function") {
161         info(
162           "CONSOLE_MESSAGE: (" +
163             levelNames[msg.logLevel] +
164             ") " +
165             msg.toString()
166         );
167       }
168     },
169   };
170   // Don't use _Services.console here as it causes one of the devtools tests
171   // to fail, probably due to initializing Services.console too early.
172   // eslint-disable-next-line mozilla/use-services
173   Cc["@mozilla.org/consoleservice;1"]
174     .getService(Ci.nsIConsoleService)
175     .registerListener(listener);
176 } catch (e) {}
178  * Date.now() is not necessarily monotonically increasing (insert sob story
179  * about times not being the right tool to use for measuring intervals of time,
180  * robarnold can tell all), so be wary of error by erring by at least
181  * _timerFuzz ms.
182  */
183 const _timerFuzz = 15;
185 function _Timer(func, delay) {
186   delay = Number(delay);
187   if (delay < 0) {
188     do_throw("do_timeout() delay must be nonnegative");
189   }
191   if (typeof func !== "function") {
192     do_throw("string callbacks no longer accepted; use a function!");
193   }
195   this._func = func;
196   this._start = Date.now();
197   this._delay = delay;
199   var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
200   timer.initWithCallback(this, delay + _timerFuzz, timer.TYPE_ONE_SHOT);
202   // Keep timer alive until it fires
203   _pendingTimers.push(timer);
205 _Timer.prototype = {
206   QueryInterface: ChromeUtils.generateQI(["nsITimerCallback"]),
208   notify(timer) {
209     _pendingTimers.splice(_pendingTimers.indexOf(timer), 1);
211     // The current nsITimer implementation can undershoot, but even if it
212     // couldn't, paranoia is probably a virtue here given the potential for
213     // random orange on tinderboxen.
214     var end = Date.now();
215     var elapsed = end - this._start;
216     if (elapsed >= this._delay) {
217       try {
218         this._func.call(null);
219       } catch (e) {
220         do_throw("exception thrown from do_timeout callback: " + e);
221       }
222       return;
223     }
225     // Timer undershot, retry with a little overshoot to try to avoid more
226     // undershoots.
227     var newDelay = this._delay - elapsed;
228     do_timeout(newDelay, this._func);
229   },
232 function _isGenerator(val) {
233   return typeof val === "object" && val && typeof val.next === "function";
236 function _do_main() {
237   if (_quit) {
238     return;
239   }
241   _testLogger.info("running event loop");
243   var tm = Cc["@mozilla.org/thread-manager;1"].getService();
245   tm.spinEventLoopUntil("Test(xpcshell/head.js:_do_main)", () => _quit);
247   tm.spinEventLoopUntilEmpty();
250 function _do_quit() {
251   _testLogger.info("exiting test");
252   _quit = true;
255 // This is useless, except to the extent that it has the side-effect of
256 // initializing the widget module, which some tests unfortunately
257 // accidentally rely on.
258 void Cc["@mozilla.org/widget/transferable;1"].createInstance();
261  * Overrides idleService with a mock.  Idle is commonly used for maintenance
262  * tasks, thus if a test uses a service that requires the idle service, it will
263  * start handling them.
264  * This behaviour would cause random failures and slowdown tests execution,
265  * for example by running database vacuum or cleanups for each test.
267  * @note Idle service is overridden by default.  If a test requires it, it will
268  *       have to call do_get_idle() function at least once before use.
269  */
270 var _fakeIdleService = {
271   get registrar() {
272     delete this.registrar;
273     return (this.registrar = Components.manager.QueryInterface(
274       Ci.nsIComponentRegistrar
275     ));
276   },
277   contractID: "@mozilla.org/widget/useridleservice;1",
278   CID: Components.ID("{9163a4ae-70c2-446c-9ac1-bbe4ab93004e}"),
280   activate: function FIS_activate() {
281     if (!this.originalCID) {
282       this.originalCID = this.registrar.contractIDToCID(this.contractID);
283       // Replace with the mock.
284       this.registrar.registerFactory(
285         this.CID,
286         "Fake Idle Service",
287         this.contractID,
288         this.factory
289       );
290     }
291   },
293   deactivate: function FIS_deactivate() {
294     if (this.originalCID) {
295       // Unregister the mock.
296       this.registrar.unregisterFactory(this.CID, this.factory);
297       // Restore original factory.
298       this.registrar.registerFactory(
299         this.originalCID,
300         "Idle Service",
301         this.contractID,
302         null
303       );
304       delete this.originalCID;
305     }
306   },
308   factory: {
309     // nsIFactory
310     createInstance(aIID) {
311       return _fakeIdleService.QueryInterface(aIID);
312     },
313     QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
314   },
316   // nsIUserIdleService
317   get idleTime() {
318     return 0;
319   },
320   addIdleObserver() {},
321   removeIdleObserver() {},
323   // eslint-disable-next-line mozilla/use-chromeutils-generateqi
324   QueryInterface(aIID) {
325     // Useful for testing purposes, see test_get_idle.js.
326     if (aIID.equals(Ci.nsIFactory)) {
327       return this.factory;
328     }
329     if (aIID.equals(Ci.nsIUserIdleService) || aIID.equals(Ci.nsISupports)) {
330       return this;
331     }
332     throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
333   },
337  * Restores the idle service factory if needed and returns the service's handle.
338  * @return A handle to the idle service.
339  */
340 function do_get_idle() {
341   _fakeIdleService.deactivate();
342   return Cc[_fakeIdleService.contractID].getService(Ci.nsIUserIdleService);
345 // Map resource://test/ to current working directory and
346 // resource://testing-common/ to the shared test modules directory.
347 function _register_protocol_handlers() {
348   let protocolHandler = _Services.io
349     .getProtocolHandler("resource")
350     .QueryInterface(Ci.nsIResProtocolHandler);
352   let curDirURI = _Services.io.newFileURI(do_get_cwd());
353   protocolHandler.setSubstitution("test", curDirURI);
355   _register_modules_protocol_handler();
358 function _register_modules_protocol_handler() {
359   if (!_TESTING_MODULES_DIR) {
360     throw new Error(
361       "Please define a path where the testing modules can be " +
362         "found in a variable called '_TESTING_MODULES_DIR' before " +
363         "head.js is included."
364     );
365   }
367   let protocolHandler = _Services.io
368     .getProtocolHandler("resource")
369     .QueryInterface(Ci.nsIResProtocolHandler);
371   let modulesFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
372   modulesFile.initWithPath(_TESTING_MODULES_DIR);
374   if (!modulesFile.exists()) {
375     throw new Error(
376       "Specified modules directory does not exist: " + _TESTING_MODULES_DIR
377     );
378   }
380   if (!modulesFile.isDirectory()) {
381     throw new Error(
382       "Specified modules directory is not a directory: " + _TESTING_MODULES_DIR
383     );
384   }
386   let modulesURI = _Services.io.newFileURI(modulesFile);
388   protocolHandler.setSubstitution("testing-common", modulesURI);
391 /* Debugging support */
392 // Used locally and by our self-tests.
393 function _setupDevToolsServer(breakpointFiles, callback) {
394   // Always allow remote debugging.
395   _Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
397   // for debugging-the-debugging, let an env var cause log spew.
398   if (_Services.env.get("DEVTOOLS_DEBUGGER_LOG")) {
399     _Services.prefs.setBoolPref("devtools.debugger.log", true);
400   }
401   if (_Services.env.get("DEVTOOLS_DEBUGGER_LOG_VERBOSE")) {
402     _Services.prefs.setBoolPref("devtools.debugger.log.verbose", true);
403   }
405   let require;
406   try {
407     ({ require } = ChromeUtils.importESModule(
408       "resource://devtools/shared/loader/Loader.sys.mjs"
409     ));
410   } catch (e) {
411     throw new Error(
412       "resource://devtools appears to be inaccessible from the " +
413         "xpcshell environment.\n" +
414         "This can usually be resolved by adding:\n" +
415         "  firefox-appdir = browser\n" +
416         "to the xpcshell.ini manifest.\n" +
417         "It is possible for this to alter test behevior by " +
418         "triggering additional browser code to run, so check " +
419         "test behavior after making this change.\n" +
420         "See also https://bugzil.la/1215378."
421     );
422   }
423   let { DevToolsServer } = require("devtools/server/devtools-server");
424   DevToolsServer.init();
425   DevToolsServer.registerAllActors();
426   let { createRootActor } = require("resource://testing-common/dbg-actors.js");
427   DevToolsServer.setRootActor(createRootActor);
428   DevToolsServer.allowChromeProcess = true;
430   const TOPICS = [
431     // An observer notification that tells us when the thread actor is ready
432     // and can accept breakpoints.
433     "devtools-thread-ready",
434     // Or when devtools are destroyed and we should stop observing.
435     "xpcshell-test-devtools-shutdown",
436   ];
437   let observe = function (subject, topic, data) {
438     if (topic === "devtools-thread-ready") {
439       const threadActor = subject.wrappedJSObject;
440       threadActor.setBreakpointOnLoad(breakpointFiles);
441     }
443     for (let topicToRemove of TOPICS) {
444       _Services.obs.removeObserver(observe, topicToRemove);
445     }
446     callback();
447   };
449   for (let topic of TOPICS) {
450     _Services.obs.addObserver(observe, topic);
451   }
453   const { SocketListener } = require("devtools/shared/security/socket");
455   return { DevToolsServer, SocketListener };
458 function _initDebugging(port) {
459   let initialized = false;
460   const { DevToolsServer, SocketListener } = _setupDevToolsServer(
461     _TEST_FILE,
462     () => {
463       initialized = true;
464     }
465   );
467   info("");
468   info("*******************************************************************");
469   info("Waiting for the debugger to connect on port " + port);
470   info("");
471   info("To connect the debugger, open a Firefox instance, select 'Connect'");
472   info("from the Developer menu and specify the port as " + port);
473   info("*******************************************************************");
474   info("");
476   const AuthenticatorType = DevToolsServer.Authenticators.get("PROMPT");
477   const authenticator = new AuthenticatorType.Server();
478   authenticator.allowConnection = () => {
479     return DevToolsServer.AuthenticationResult.ALLOW;
480   };
481   const socketOptions = {
482     authenticator,
483     portOrPath: port,
484   };
486   const listener = new SocketListener(DevToolsServer, socketOptions);
487   listener.open();
489   // spin an event loop until the debugger connects.
490   const tm = Cc["@mozilla.org/thread-manager;1"].getService();
491   tm.spinEventLoopUntil("Test(xpcshell/head.js:_initDebugging)", () => {
492     if (initialized) {
493       return true;
494     }
495     info("Still waiting for debugger to connect...");
496     return false;
497   });
498   // NOTE: if you want to debug the harness itself, you can now add a 'debugger'
499   // statement anywhere and it will stop - but we've already added a breakpoint
500   // for the first line of the test scripts, so we just continue...
501   info("Debugger connected, starting test execution");
504 function _execute_test() {
505   if (typeof _TEST_CWD != "undefined") {
506     try {
507       changeTestShellDir(_TEST_CWD);
508     } catch (e) {
509       _testLogger.error(_exception_message(e));
510     }
511   }
512   if (runningInParent && _AppConstants.platform == "android") {
513     try {
514       // GeckoView initialization needs the profile
515       do_get_profile(true);
516       // Wake up GeckoViewStartup
517       let geckoViewStartup = Cc["@mozilla.org/geckoview/startup;1"].getService(
518         Ci.nsIObserver
519       );
520       geckoViewStartup.observe(null, "profile-after-change", null);
521       geckoViewStartup.observe(null, "app-startup", null);
523       // Glean needs to be initialized for metric recording & tests to work.
524       // Usually this happens through Glean Kotlin,
525       // but for xpcshell tests we initialize it from here.
526       _Services.fog.initializeFOG();
527     } catch (ex) {
528       do_throw(`Failed to initialize GeckoView: ${ex}`, ex.stack);
529     }
530   }
532   // _JSDEBUGGER_PORT is dynamically defined by <runxpcshelltests.py>.
533   if (_JSDEBUGGER_PORT) {
534     try {
535       _initDebugging(_JSDEBUGGER_PORT);
536     } catch (ex) {
537       // Fail the test run immediately if debugging is requested but fails, so
538       // that the failure state is more obvious.
539       do_throw(`Failed to initialize debugging: ${ex}`, ex.stack);
540     }
541   }
543   _register_protocol_handlers();
545   // Override idle service by default.
546   // Call do_get_idle() to restore the factory and get the service.
547   _fakeIdleService.activate();
549   _PromiseTestUtils.init();
551   let coverageCollector = null;
552   if (typeof _JSCOV_DIR === "string") {
553     let _CoverageCollector = ChromeUtils.importESModule(
554       "resource://testing-common/CoverageUtils.sys.mjs"
555     ).CoverageCollector;
556     coverageCollector = new _CoverageCollector(_JSCOV_DIR);
557   }
559   let startTime = Cu.now();
561   // _HEAD_FILES is dynamically defined by <runxpcshelltests.py>.
562   _load_files(_HEAD_FILES);
563   // _TEST_FILE is dynamically defined by <runxpcshelltests.py>.
564   _load_files(_TEST_FILE);
566   // Tack Assert.sys.mjs methods to the current scope.
567   this.Assert = Assert;
568   for (let func in Assert) {
569     this[func] = Assert[func].bind(Assert);
570   }
572   const { PerTestCoverageUtils } = ChromeUtils.importESModule(
573     "resource://testing-common/PerTestCoverageUtils.sys.mjs"
574   );
576   if (runningInParent) {
577     PerTestCoverageUtils.beforeTestSync();
578   }
580   try {
581     do_test_pending("MAIN run_test");
582     // Check if run_test() is defined. If defined, run it.
583     // Else, call run_next_test() directly to invoke tests
584     // added by add_test() and add_task().
585     if (typeof run_test === "function") {
586       run_test();
587     } else {
588       run_next_test();
589     }
591     do_test_finished("MAIN run_test");
592     _do_main();
593     _PromiseTestUtils.assertNoUncaughtRejections();
595     if (coverageCollector != null) {
596       coverageCollector.recordTestCoverage(_TEST_FILE[0]);
597     }
599     if (runningInParent) {
600       PerTestCoverageUtils.afterTestSync();
601     }
602   } catch (e) {
603     _passed = false;
604     // do_check failures are already logged and set _quit to true and throw
605     // NS_ERROR_ABORT. If both of those are true it is likely this exception
606     // has already been logged so there is no need to log it again. It's
607     // possible that this will mask an NS_ERROR_ABORT that happens after a
608     // do_check failure though.
610     if (!_quit || e.result != Cr.NS_ERROR_ABORT) {
611       let extra = {};
612       if (e.fileName) {
613         extra.source_file = e.fileName;
614         if (e.lineNumber) {
615           extra.line_number = e.lineNumber;
616         }
617       } else {
618         extra.source_file = "xpcshell/head.js";
619       }
620       let message = _exception_message(e);
621       if (e.stack) {
622         extra.stack = _format_stack(e.stack);
623       }
624       _testLogger.error(message, extra);
625     }
626   } finally {
627     if (coverageCollector != null) {
628       coverageCollector.finalize();
629     }
630   }
632   // Execute all of our cleanup functions.
633   let reportCleanupError = function (ex) {
634     let stack, filename;
635     if (ex && typeof ex == "object" && "stack" in ex) {
636       stack = ex.stack;
637     } else {
638       stack = Components.stack.caller;
639     }
640     if (stack instanceof Ci.nsIStackFrame) {
641       filename = stack.filename;
642     } else if (ex.fileName) {
643       filename = ex.fileName;
644     }
645     _testLogger.error(_exception_message(ex), {
646       stack: _format_stack(stack),
647       source_file: filename,
648     });
649   };
651   let complete = !_cleanupFunctions.length;
652   let cleanupStartTime = complete ? 0 : Cu.now();
653   (async () => {
654     for (let func of _cleanupFunctions.reverse()) {
655       try {
656         let result = await func();
657         if (_isGenerator(result)) {
658           Assert.ok(false, "Cleanup function returned a generator");
659         }
660       } catch (ex) {
661         reportCleanupError(ex);
662       }
663     }
664     _cleanupFunctions = [];
665   })()
666     .catch(reportCleanupError)
667     .then(() => (complete = true));
668   _Services.tm.spinEventLoopUntil(
669     "Test(xpcshell/head.js:_execute_test)",
670     () => complete
671   );
672   if (cleanupStartTime) {
673     ChromeUtils.addProfilerMarker(
674       "xpcshell-test",
675       { category: "Test", startTime: cleanupStartTime },
676       "Cleanup functions"
677     );
678   }
680   ChromeUtils.addProfilerMarker(
681     "xpcshell-test",
682     { category: "Test", startTime },
683     _TEST_NAME
684   );
685   _Services.obs.notifyObservers(null, "test-complete");
687   // Restore idle service to avoid leaks.
688   _fakeIdleService.deactivate();
690   if (
691     globalThis.hasOwnProperty("storage") &&
692     StorageManager.isInstance(globalThis.storage)
693   ) {
694     globalThis.storage.shutdown();
695   }
697   if (_profileInitialized) {
698     // Since we have a profile, we will notify profile shutdown topics at
699     // the end of the current test, to ensure correct cleanup on shutdown.
700     _Services.startup.advanceShutdownPhase(
701       _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNNETTEARDOWN
702     );
703     _Services.startup.advanceShutdownPhase(
704       _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNTEARDOWN
705     );
706     _Services.startup.advanceShutdownPhase(
707       _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWN
708     );
709     _Services.startup.advanceShutdownPhase(
710       _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNQM
711     );
713     _profileInitialized = false;
714   }
716   try {
717     _PromiseTestUtils.ensureDOMPromiseRejectionsProcessed();
718     _PromiseTestUtils.assertNoUncaughtRejections();
719     _PromiseTestUtils.assertNoMoreExpectedRejections();
720   } finally {
721     // It's important to terminate the module to avoid crashes on shutdown.
722     _PromiseTestUtils.uninit();
723   }
725   // Skip the normal shutdown path for optimized builds that don't do leak checking.
726   if (
727     runningInParent &&
728     !_AppConstants.RELEASE_OR_BETA &&
729     !_AppConstants.DEBUG &&
730     !_AppConstants.MOZ_CODE_COVERAGE &&
731     !_AppConstants.ASAN &&
732     !_AppConstants.TSAN
733   ) {
734     Cu.exitIfInAutomation();
735   }
739  * Loads files.
741  * @param aFiles Array of files to load.
742  */
743 function _load_files(aFiles) {
744   function load_file(element, index, array) {
745     try {
746       let startTime = Cu.now();
747       load(element);
748       ChromeUtils.addProfilerMarker(
749         "load_file",
750         { category: "Test", startTime },
751         element.replace(/.*\/_?tests\/xpcshell\//, "")
752       );
753     } catch (e) {
754       let extra = {
755         source_file: element,
756       };
757       if (e.stack) {
758         extra.stack = _format_stack(e.stack);
759       }
760       _testLogger.error(_exception_message(e), extra);
761     }
762   }
764   aFiles.forEach(load_file);
767 function _wrap_with_quotes_if_necessary(val) {
768   return typeof val == "string" ? '"' + val + '"' : val;
771 /* ************* Functions to be used from the tests ************* */
774  * Prints a message to the output log.
775  */
776 function info(msg, data) {
777   ChromeUtils.addProfilerMarker("INFO", { category: "Test" }, msg);
778   msg = _wrap_with_quotes_if_necessary(msg);
779   data = data ? data : null;
780   _testLogger.info(msg, data);
784  * Calls the given function at least the specified number of milliseconds later.
785  * The callback will not undershoot the given time, but it might overshoot --
786  * don't expect precision!
788  * @param delay : uint
789  *   the number of milliseconds to delay
790  * @param callback : function() : void
791  *   the function to call
792  */
793 function do_timeout(delay, func) {
794   new _Timer(func, Number(delay));
797 function executeSoon(callback, aName) {
798   let funcName = aName ? aName : callback.name;
799   do_test_pending(funcName);
801   _Services.tm.dispatchToMainThread({
802     run() {
803       try {
804         callback();
805       } catch (e) {
806         // do_check failures are already logged and set _quit to true and throw
807         // NS_ERROR_ABORT. If both of those are true it is likely this exception
808         // has already been logged so there is no need to log it again. It's
809         // possible that this will mask an NS_ERROR_ABORT that happens after a
810         // do_check failure though.
811         if (!_quit || e.result != Cr.NS_ERROR_ABORT) {
812           let stack = e.stack ? _format_stack(e.stack) : null;
813           _testLogger.testStatus(
814             _TEST_NAME,
815             funcName,
816             "FAIL",
817             "PASS",
818             _exception_message(e),
819             stack
820           );
821           _do_quit();
822         }
823       } finally {
824         do_test_finished(funcName);
825       }
826     },
827   });
831  * Shows an error message and the current stack and aborts the test.
833  * @param error  A message string or an Error object.
834  * @param stack  null or nsIStackFrame object or a string containing
835  *               \n separated stack lines (as in Error().stack).
836  */
837 function do_throw(error, stack) {
838   let filename = "";
839   // If we didn't get passed a stack, maybe the error has one
840   // otherwise get it from our call context
841   stack = stack || error.stack || Components.stack.caller;
843   if (stack instanceof Ci.nsIStackFrame) {
844     filename = stack.filename;
845   } else if (error.fileName) {
846     filename = error.fileName;
847   }
849   _testLogger.error(_exception_message(error), {
850     source_file: filename,
851     stack: _format_stack(stack),
852   });
853   ChromeUtils.addProfilerMarker(
854     "ERROR",
855     { category: "Test", captureStack: true },
856     _exception_message(error)
857   );
858   _abort_failed_test();
861 function _abort_failed_test() {
862   // Called to abort the test run after all failures are logged.
863   _passed = false;
864   _do_quit();
865   throw Components.Exception("", Cr.NS_ERROR_ABORT);
868 function _format_stack(stack) {
869   let normalized;
870   if (stack instanceof Ci.nsIStackFrame) {
871     let frames = [];
872     for (let frame = stack; frame; frame = frame.caller) {
873       frames.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
874     }
875     normalized = frames.join("\n");
876   } else {
877     normalized = "" + stack;
878   }
879   return normalized;
882 // Make a nice display string from an object that behaves
883 // like Error
884 function _exception_message(ex) {
885   if (ex === undefined) {
886     return "`undefined` exception, maybe from an empty reject()?";
887   }
888   let message = "";
889   if (ex.name) {
890     message = ex.name + ": ";
891   }
892   if (ex.message) {
893     message += ex.message;
894   }
895   if (ex.fileName) {
896     message += " at " + ex.fileName;
897     if (ex.lineNumber) {
898       message += ":" + ex.lineNumber;
899     }
900   }
901   if (message !== "") {
902     return message;
903   }
904   // Force ex to be stringified
905   return "" + ex;
908 function do_report_unexpected_exception(ex, text) {
909   let filename = Components.stack.caller.filename;
910   text = text ? text + " - " : "";
912   _passed = false;
913   _testLogger.error(text + "Unexpected exception " + _exception_message(ex), {
914     source_file: filename,
915     stack: _format_stack(ex?.stack),
916   });
917   _do_quit();
918   throw Components.Exception("", Cr.NS_ERROR_ABORT);
921 function do_note_exception(ex, text) {
922   let filename = Components.stack.caller.filename;
923   _testLogger.info(text + "Swallowed exception " + _exception_message(ex), {
924     source_file: filename,
925     stack: _format_stack(ex?.stack),
926   });
929 function do_report_result(passed, text, stack, todo) {
930   // Match names like head.js, head_foo.js, and foo_head.js, but not
931   // test_headache.js
932   while (/(\/head(_.+)?|head)\.js$/.test(stack.filename) && stack.caller) {
933     stack = stack.caller;
934   }
936   let name = _gRunningTest ? _gRunningTest.name : stack.name;
937   let message;
938   if (name) {
939     message = "[" + name + " : " + stack.lineNumber + "] " + text;
940   } else {
941     message = text;
942   }
944   if (passed) {
945     if (todo) {
946       _testLogger.testStatus(
947         _TEST_NAME,
948         name,
949         "PASS",
950         "FAIL",
951         message,
952         _format_stack(stack)
953       );
954       ChromeUtils.addProfilerMarker(
955         "UNEXPECTED-PASS",
956         { category: "Test" },
957         message
958       );
959       _abort_failed_test();
960     } else {
961       _testLogger.testStatus(_TEST_NAME, name, "PASS", "PASS", message);
962       ChromeUtils.addProfilerMarker("PASS", { category: "Test" }, message);
963     }
964   } else if (todo) {
965     _testLogger.testStatus(_TEST_NAME, name, "FAIL", "FAIL", message);
966     ChromeUtils.addProfilerMarker("TODO", { category: "Test" }, message);
967   } else {
968     _testLogger.testStatus(
969       _TEST_NAME,
970       name,
971       "FAIL",
972       "PASS",
973       message,
974       _format_stack(stack)
975     );
976     ChromeUtils.addProfilerMarker("FAIL", { category: "Test" }, message);
977     _abort_failed_test();
978   }
981 function _do_check_eq(left, right, stack, todo) {
982   if (!stack) {
983     stack = Components.stack.caller;
984   }
986   var text =
987     _wrap_with_quotes_if_necessary(left) +
988     " == " +
989     _wrap_with_quotes_if_necessary(right);
990   do_report_result(left == right, text, stack, todo);
993 function todo_check_eq(left, right, stack) {
994   if (!stack) {
995     stack = Components.stack.caller;
996   }
998   _do_check_eq(left, right, stack, true);
1001 function todo_check_true(condition, stack) {
1002   if (!stack) {
1003     stack = Components.stack.caller;
1004   }
1006   todo_check_eq(condition, true, stack);
1009 function todo_check_false(condition, stack) {
1010   if (!stack) {
1011     stack = Components.stack.caller;
1012   }
1014   todo_check_eq(condition, false, stack);
1017 function todo_check_null(condition, stack = Components.stack.caller) {
1018   todo_check_eq(condition, null, stack);
1021 // Check that |func| throws an nsIException that has
1022 // |Components.results[resultName]| as the value of its 'result' property.
1023 function do_check_throws_nsIException(
1024   func,
1025   resultName,
1026   stack = Components.stack.caller,
1027   todo = false
1028 ) {
1029   let expected = Cr[resultName];
1030   if (typeof expected !== "number") {
1031     do_throw(
1032       "do_check_throws_nsIException requires a Components.results" +
1033         " property name, not " +
1034         uneval(resultName),
1035       stack
1036     );
1037   }
1039   let msg =
1040     "do_check_throws_nsIException: func should throw" +
1041     " an nsIException whose 'result' is Components.results." +
1042     resultName;
1044   try {
1045     func();
1046   } catch (ex) {
1047     if (!(ex instanceof Ci.nsIException) || ex.result !== expected) {
1048       do_report_result(
1049         false,
1050         msg + ", threw " + legible_exception(ex) + " instead",
1051         stack,
1052         todo
1053       );
1054     }
1056     do_report_result(true, msg, stack, todo);
1057     return;
1058   }
1060   // Call this here, not in the 'try' clause, so do_report_result's own
1061   // throw doesn't get caught by our 'catch' clause.
1062   do_report_result(false, msg + ", but returned normally", stack, todo);
1065 // Produce a human-readable form of |exception|. This looks up
1066 // Components.results values, tries toString methods, and so on.
1067 function legible_exception(exception) {
1068   switch (typeof exception) {
1069     case "object":
1070       if (exception instanceof Ci.nsIException) {
1071         return "nsIException instance: " + uneval(exception.toString());
1072       }
1073       return exception.toString();
1075     case "number":
1076       for (let name in Cr) {
1077         if (exception === Cr[name]) {
1078           return "Components.results." + name;
1079         }
1080       }
1082     // Fall through.
1083     default:
1084       return uneval(exception);
1085   }
1088 function do_check_instanceof(
1089   value,
1090   constructor,
1091   stack = Components.stack.caller,
1092   todo = false
1093 ) {
1094   do_report_result(
1095     value instanceof constructor,
1096     "value should be an instance of " + constructor.name,
1097     stack,
1098     todo
1099   );
1102 function todo_check_instanceof(
1103   value,
1104   constructor,
1105   stack = Components.stack.caller
1106 ) {
1107   do_check_instanceof(value, constructor, stack, true);
1110 function do_test_pending(aName) {
1111   ++_tests_pending;
1113   _testLogger.info(
1114     "(xpcshell/head.js) | test" +
1115       (aName ? " " + aName : "") +
1116       " pending (" +
1117       _tests_pending +
1118       ")"
1119   );
1122 function do_test_finished(aName) {
1123   _testLogger.info(
1124     "(xpcshell/head.js) | test" +
1125       (aName ? " " + aName : "") +
1126       " finished (" +
1127       _tests_pending +
1128       ")"
1129   );
1130   if (--_tests_pending == 0) {
1131     _do_quit();
1132   }
1135 function do_get_file(path, allowNonexistent) {
1136   try {
1137     let lf = _Services.dirsvc.get("CurWorkD", Ci.nsIFile);
1139     let bits = path.split("/");
1140     for (let i = 0; i < bits.length; i++) {
1141       if (bits[i]) {
1142         if (bits[i] == "..") {
1143           lf = lf.parent;
1144         } else {
1145           lf.append(bits[i]);
1146         }
1147       }
1148     }
1150     if (!allowNonexistent && !lf.exists()) {
1151       // Not using do_throw(): caller will continue.
1152       _passed = false;
1153       var stack = Components.stack.caller;
1154       _testLogger.error(
1155         "[" +
1156           stack.name +
1157           " : " +
1158           stack.lineNumber +
1159           "] " +
1160           lf.path +
1161           " does not exist"
1162       );
1163     }
1165     return lf;
1166   } catch (ex) {
1167     do_throw(ex.toString(), Components.stack.caller);
1168   }
1170   return null;
1173 // do_get_cwd() isn't exactly self-explanatory, so provide a helper
1174 function do_get_cwd() {
1175   return do_get_file("");
1178 function do_load_manifest(path) {
1179   var lf = do_get_file(path);
1180   const nsIComponentRegistrar = Ci.nsIComponentRegistrar;
1181   Assert.ok(Components.manager instanceof nsIComponentRegistrar);
1182   // Previous do_check_true() is not a test check.
1183   Components.manager.autoRegister(lf);
1187  * Parse a DOM document.
1189  * @param aPath File path to the document.
1190  * @param aType Content type to use in DOMParser.
1192  * @return Document from the file.
1193  */
1194 function do_parse_document(aPath, aType) {
1195   switch (aType) {
1196     case "application/xhtml+xml":
1197     case "application/xml":
1198     case "text/xml":
1199       break;
1201     default:
1202       do_throw(
1203         "type: expected application/xhtml+xml, application/xml or text/xml," +
1204           " got '" +
1205           aType +
1206           "'",
1207         Components.stack.caller
1208       );
1209   }
1211   let file = do_get_file(aPath);
1212   let url = _Services.io.newFileURI(file).spec;
1213   file = null;
1214   return new Promise((resolve, reject) => {
1215     let xhr = new XMLHttpRequest();
1216     xhr.open("GET", url);
1217     xhr.responseType = "document";
1218     xhr.onerror = reject;
1219     xhr.onload = () => {
1220       resolve(xhr.response);
1221     };
1222     xhr.send();
1223   });
1227  * Registers a function that will run when the test harness is done running all
1228  * tests.
1230  * @param aFunction
1231  *        The function to be called when the test harness has finished running.
1232  */
1233 function registerCleanupFunction(aFunction) {
1234   _cleanupFunctions.push(aFunction);
1238  * Returns the directory for a temp dir, which is created by the
1239  * test harness. Every test gets its own temp dir.
1241  * @return nsIFile of the temporary directory
1242  */
1243 function do_get_tempdir() {
1244   // the python harness sets this in the environment for us
1245   let path = _Services.env.get("XPCSHELL_TEST_TEMP_DIR");
1246   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1247   file.initWithPath(path);
1248   return file;
1252  * Returns the directory for crashreporter minidumps.
1254  * @return nsIFile of the minidump directory
1255  */
1256 function do_get_minidumpdir() {
1257   // the python harness may set this in the environment for us
1258   let path = _Services.env.get("XPCSHELL_MINIDUMP_DIR");
1259   if (path) {
1260     let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1261     file.initWithPath(path);
1262     return file;
1263   }
1264   return do_get_tempdir();
1268  * Registers a directory with the profile service,
1269  * and return the directory as an nsIFile.
1271  * @param notifyProfileAfterChange Whether to notify for "profile-after-change".
1272  * @return nsIFile of the profile directory.
1273  */
1274 function do_get_profile(notifyProfileAfterChange = false) {
1275   if (!runningInParent) {
1276     _testLogger.info("Ignoring profile creation from child process.");
1277     return null;
1278   }
1280   // the python harness sets this in the environment for us
1281   let profd = Services.env.get("XPCSHELL_TEST_PROFILE_DIR");
1282   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1283   file.initWithPath(profd);
1285   let provider = {
1286     getFile(prop, persistent) {
1287       persistent.value = true;
1288       if (
1289         prop == "ProfD" ||
1290         prop == "ProfLD" ||
1291         prop == "ProfDS" ||
1292         prop == "ProfLDS" ||
1293         prop == "TmpD"
1294       ) {
1295         return file.clone();
1296       }
1297       return null;
1298     },
1299     QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]),
1300   };
1301   _Services.dirsvc
1302     .QueryInterface(Ci.nsIDirectoryService)
1303     .registerProvider(provider);
1305   try {
1306     _Services.dirsvc.undefine("TmpD");
1307   } catch (e) {
1308     // This throws if the key is not already registered, but that
1309     // doesn't matter.
1310     if (e.result != Cr.NS_ERROR_FAILURE) {
1311       throw e;
1312     }
1313   }
1315   // We need to update the crash events directory when the profile changes.
1316   if (runningInParent && "@mozilla.org/toolkit/crash-reporter;1" in Cc) {
1317     // Intentially access the crash reporter service directly for this.
1318     // eslint-disable-next-line mozilla/use-services
1319     let crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService(
1320       Ci.nsICrashReporter
1321     );
1322     crashReporter.UpdateCrashEventsDir();
1323   }
1325   if (!_profileInitialized) {
1326     _Services.obs.notifyObservers(
1327       null,
1328       "profile-do-change",
1329       "xpcshell-do-get-profile"
1330     );
1331     _profileInitialized = true;
1332     if (notifyProfileAfterChange) {
1333       _Services.obs.notifyObservers(
1334         null,
1335         "profile-after-change",
1336         "xpcshell-do-get-profile"
1337       );
1338     }
1339   }
1341   // The methods of 'provider' will retain this scope so null out everything
1342   // to avoid spurious leak reports.
1343   profd = null;
1344   provider = null;
1345   return file.clone();
1349  * This function loads head.js (this file) in the child process, so that all
1350  * functions defined in this file (do_throw, etc) are available to subsequent
1351  * sendCommand calls.  It also sets various constants used by these functions.
1353  * (Note that you may use sendCommand without calling this function first;  you
1354  * simply won't have any of the functions in this file available.)
1355  */
1356 function do_load_child_test_harness() {
1357   // Make sure this isn't called from child process
1358   if (!runningInParent) {
1359     do_throw("run_test_in_child cannot be called from child!");
1360   }
1362   // Allow to be called multiple times, but only run once
1363   if (typeof do_load_child_test_harness.alreadyRun != "undefined") {
1364     return;
1365   }
1366   do_load_child_test_harness.alreadyRun = 1;
1368   _XPCSHELL_PROCESS = "parent";
1370   let command =
1371     "const _HEAD_JS_PATH=" +
1372     uneval(_HEAD_JS_PATH) +
1373     "; " +
1374     "const _HEAD_FILES=" +
1375     uneval(_HEAD_FILES) +
1376     "; " +
1377     "const _MOZINFO_JS_PATH=" +
1378     uneval(_MOZINFO_JS_PATH) +
1379     "; " +
1380     "const _TEST_NAME=" +
1381     uneval(_TEST_NAME) +
1382     "; " +
1383     // We'll need more magic to get the debugger working in the child
1384     "const _JSDEBUGGER_PORT=0; " +
1385     "_XPCSHELL_PROCESS='child';";
1387   if (typeof _JSCOV_DIR === "string") {
1388     command += " const _JSCOV_DIR=" + uneval(_JSCOV_DIR) + ";";
1389   }
1391   if (typeof _TEST_CWD != "undefined") {
1392     command += " const _TEST_CWD=" + uneval(_TEST_CWD) + ";";
1393   }
1395   if (_TESTING_MODULES_DIR) {
1396     command +=
1397       " const _TESTING_MODULES_DIR=" + uneval(_TESTING_MODULES_DIR) + ";";
1398   }
1400   command += " load(_HEAD_JS_PATH);";
1401   sendCommand(command);
1405  * Runs an entire xpcshell unit test in a child process (rather than in chrome,
1406  * which is the default).
1408  * This function returns immediately, before the test has completed.
1410  * @param testFile
1411  *        The name of the script to run.  Path format same as load().
1412  * @param optionalCallback.
1413  *        Optional function to be called (in parent) when test on child is
1414  *        complete.  If provided, the function must call do_test_finished();
1415  * @return Promise Resolved when the test in the child is complete.
1416  */
1417 function run_test_in_child(testFile, optionalCallback) {
1418   return new Promise(resolve => {
1419     var callback = () => {
1420       resolve();
1421       if (typeof optionalCallback == "undefined") {
1422         do_test_finished();
1423       } else {
1424         optionalCallback();
1425       }
1426     };
1428     do_load_child_test_harness();
1430     var testPath = do_get_file(testFile).path.replace(/\\/g, "/");
1431     do_test_pending("run in child");
1432     sendCommand(
1433       "_testLogger.info('CHILD-TEST-STARTED'); " +
1434         "const _TEST_FILE=['" +
1435         testPath +
1436         "']; " +
1437         "_execute_test(); " +
1438         "_testLogger.info('CHILD-TEST-COMPLETED');",
1439       callback
1440     );
1441   });
1445  * Execute a given function as soon as a particular cross-process message is received.
1446  * Must be paired with do_send_remote_message or equivalent ProcessMessageManager calls.
1448  * @param optionalCallback
1449  *        Optional callback that is invoked when the message is received.  If provided,
1450  *        the function must call do_test_finished().
1451  * @return Promise Promise that is resolved when the message is received.
1452  */
1453 function do_await_remote_message(name, optionalCallback) {
1454   return new Promise(resolve => {
1455     var listener = {
1456       receiveMessage(message) {
1457         if (message.name == name) {
1458           mm.removeMessageListener(name, listener);
1459           resolve(message.data);
1460           if (optionalCallback) {
1461             optionalCallback(message.data);
1462           } else {
1463             do_test_finished();
1464           }
1465         }
1466       },
1467     };
1469     var mm;
1470     if (runningInParent) {
1471       mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService();
1472     } else {
1473       mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService();
1474     }
1475     do_test_pending();
1476     mm.addMessageListener(name, listener);
1477   });
1481  * Asynchronously send a message to all remote processes. Pairs with do_await_remote_message
1482  * or equivalent ProcessMessageManager listeners.
1483  */
1484 function do_send_remote_message(name, data) {
1485   var mm;
1486   var sender;
1487   if (runningInParent) {
1488     mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService();
1489     sender = "broadcastAsyncMessage";
1490   } else {
1491     mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService();
1492     sender = "sendAsyncMessage";
1493   }
1494   mm[sender](name, data);
1498  * Schedules and awaits a precise GC, and forces CC, `maxCount` number of times.
1499  * @param maxCount
1500  *        How many times GC and CC should be scheduled.
1501  */
1502 async function schedulePreciseGCAndForceCC(maxCount) {
1503   for (let count = 0; count < maxCount; count++) {
1504     await new Promise(resolve => Cu.schedulePreciseGC(resolve));
1505     Cu.forceCC();
1506   }
1510  * Add a test function to the list of tests that are to be run asynchronously.
1512  * @param funcOrProperties
1513  *        A function to be run or an object represents test properties.
1514  *        Supported properties:
1515  *          skip_if : An arrow function which has an expression to be
1516  *                    evaluated whether the test is skipped or not.
1517  *          pref_set: An array of preferences to set for the test, reset at end of test.
1518  * @param func
1519  *        A function to be run only if the funcOrProperies is not a function.
1520  * @param isTask
1521  *        Optional flag that indicates whether `func` is a task. Defaults to `false`.
1522  * @param isSetup
1523  *        Optional flag that indicates whether `func` is a setup task. Defaults to `false`.
1524  *        Implies isTask.
1526  * Each test function must call run_next_test() when it's done. Test files
1527  * should call run_next_test() in their run_test function to execute all
1528  * async tests.
1530  * @return the test function that was passed in.
1531  */
1532 var _gSupportedProperties = ["skip_if", "pref_set"];
1533 var _gTests = [];
1534 var _gRunOnlyThisTest = null;
1535 function add_test(
1536   properties,
1537   func = properties,
1538   isTask = false,
1539   isSetup = false
1540 ) {
1541   if (isSetup) {
1542     isTask = true;
1543   }
1544   if (typeof properties == "function") {
1545     properties = { isTask, isSetup };
1546     _gTests.push([properties, func]);
1547   } else if (typeof properties == "object") {
1548     // Ensure only documented properties are in the object.
1549     for (let prop of Object.keys(properties)) {
1550       if (!_gSupportedProperties.includes(prop)) {
1551         do_throw(`Task property is not supported: ${prop}`);
1552       }
1553     }
1554     properties.isTask = isTask;
1555     properties.isSetup = isSetup;
1556     _gTests.push([properties, func]);
1557   } else {
1558     do_throw("add_test() should take a function or an object and a function");
1559   }
1560   func.skip = () => (properties.skip_if = () => true);
1561   func.only = () => (_gRunOnlyThisTest = func);
1562   return func;
1566  * Add a test function which is a Task function.
1568  * @param funcOrProperties
1569  *        An async function to be run or an object represents test properties.
1570  *        Supported properties:
1571  *          skip_if : An arrow function which has an expression to be
1572  *                    evaluated whether the test is skipped or not.
1573  *          pref_set: An array of preferences to set for the test, reset at end of test.
1574  * @param func
1575  *        An async function to be run only if the funcOrProperies is not a function.
1577  * Task functions are functions fed into Task.jsm's Task.spawn(). They are async
1578  * functions that emit promises.
1580  * If an exception is thrown, a do_check_* comparison fails, or if a rejected
1581  * promise is yielded, the test function aborts immediately and the test is
1582  * reported as a failure.
1584  * Unlike add_test(), there is no need to call run_next_test(). The next test
1585  * will run automatically as soon the task function is exhausted. To trigger
1586  * premature (but successful) termination of the function or simply return.
1588  * Example usage:
1590  * add_task(async function test() {
1591  *   let result = await Promise.resolve(true);
1593  *   do_check_true(result);
1595  *   let secondary = await someFunctionThatReturnsAPromise(result);
1596  *   do_check_eq(secondary, "expected value");
1597  * });
1599  * add_task(async function test_early_return() {
1600  *   let result = await somethingThatReturnsAPromise();
1602  *   if (!result) {
1603  *     // Test is ended immediately, with success.
1604  *     return;
1605  *   }
1607  *   do_check_eq(result, "foo");
1608  * });
1610  * add_task({
1611  *   skip_if: () => !("@mozilla.org/telephony/volume-service;1" in Components.classes),
1612  *   pref_set: [["some.pref", "value"], ["another.pref", true]],
1613  * }, async function test_volume_service() {
1614  *   let volumeService = Cc["@mozilla.org/telephony/volume-service;1"]
1615  *     .getService(Ci.nsIVolumeService);
1616  *   ...
1617  * });
1618  */
1619 function add_task(properties, func = properties) {
1620   return add_test(properties, func, true);
1624  * add_setup is like add_task, but creates setup tasks.
1625  */
1626 function add_setup(properties, func = properties) {
1627   return add_test(properties, func, true, true);
1630 const _setTaskPrefs = prefs => {
1631   for (let [pref, value] of prefs) {
1632     if (value === undefined) {
1633       // Clear any pref that didn't have a user value.
1634       info(`Clearing pref "${pref}"`);
1635       _Services.prefs.clearUserPref(pref);
1636       continue;
1637     }
1639     info(`Setting pref "${pref}": ${value}`);
1640     switch (typeof value) {
1641       case "boolean":
1642         _Services.prefs.setBoolPref(pref, value);
1643         break;
1644       case "number":
1645         _Services.prefs.setIntPref(pref, value);
1646         break;
1647       case "string":
1648         _Services.prefs.setStringPref(pref, value);
1649         break;
1650       default:
1651         throw new Error("runWithPrefs doesn't support this pref type yet");
1652     }
1653   }
1656 const _getTaskPrefs = prefs => {
1657   return prefs.map(([pref, value]) => {
1658     info(`Getting initial pref value for "${pref}"`);
1659     if (!_Services.prefs.prefHasUserValue(pref)) {
1660       // Check if the pref doesn't have a user value.
1661       return [pref, undefined];
1662     }
1663     switch (typeof value) {
1664       case "boolean":
1665         return [pref, _Services.prefs.getBoolPref(pref)];
1666       case "number":
1667         return [pref, _Services.prefs.getIntPref(pref)];
1668       case "string":
1669         return [pref, _Services.prefs.getStringPref(pref)];
1670       default:
1671         throw new Error("runWithPrefs doesn't support this pref type yet");
1672     }
1673   });
1677  * Runs the next test function from the list of async tests.
1678  */
1679 var _gRunningTest = null;
1680 var _gTestIndex = 0; // The index of the currently running test.
1681 var _gTaskRunning = false;
1682 var _gSetupRunning = false;
1683 function run_next_test() {
1684   if (_gTaskRunning) {
1685     throw new Error(
1686       "run_next_test() called from an add_task() test function. " +
1687         "run_next_test() should not be called from inside add_setup() or add_task() " +
1688         "under any circumstances!"
1689     );
1690   }
1692   if (_gSetupRunning) {
1693     throw new Error(
1694       "run_next_test() called from an add_setup() test function. " +
1695         "run_next_test() should not be called from inside add_setup() or add_task() " +
1696         "under any circumstances!"
1697     );
1698   }
1700   function _run_next_test() {
1701     if (_gTestIndex < _gTests.length) {
1702       // Check for uncaught rejections as early and often as possible.
1703       _PromiseTestUtils.assertNoUncaughtRejections();
1704       let _properties;
1705       [_properties, _gRunningTest] = _gTests[_gTestIndex++];
1707       // Must set to pending before we check for skip, so that we keep the
1708       // running counts correct.
1709       _testLogger.info(
1710         `${_TEST_NAME} | Starting ${_properties.isSetup ? "setup " : ""}${
1711           _gRunningTest.name
1712         }`
1713       );
1714       do_test_pending(_gRunningTest.name);
1716       if (
1717         (typeof _properties.skip_if == "function" && _properties.skip_if()) ||
1718         (_gRunOnlyThisTest &&
1719           _gRunningTest != _gRunOnlyThisTest &&
1720           !_properties.isSetup)
1721       ) {
1722         let _condition = _gRunOnlyThisTest
1723           ? "only one task may run."
1724           : _properties.skip_if.toSource().replace(/\(\)\s*=>\s*/, "");
1725         if (_condition == "true") {
1726           _condition = "explicitly skipped.";
1727         }
1728         let _message =
1729           _gRunningTest.name +
1730           " skipped because the following conditions were" +
1731           " met: (" +
1732           _condition +
1733           ")";
1734         _testLogger.testStatus(
1735           _TEST_NAME,
1736           _gRunningTest.name,
1737           "SKIP",
1738           "SKIP",
1739           _message
1740         );
1741         executeSoon(run_next_test);
1742         return;
1743       }
1745       let initialPrefsValues = [];
1746       if (_properties.pref_set) {
1747         initialPrefsValues = _getTaskPrefs(_properties.pref_set);
1748         _setTaskPrefs(_properties.pref_set);
1749       }
1751       if (_properties.isTask) {
1752         if (_properties.isSetup) {
1753           _gSetupRunning = true;
1754         } else {
1755           _gTaskRunning = true;
1756         }
1757         let startTime = Cu.now();
1758         (async () => _gRunningTest())().then(
1759           result => {
1760             _gTaskRunning = _gSetupRunning = false;
1761             ChromeUtils.addProfilerMarker(
1762               "task",
1763               { category: "Test", startTime },
1764               _gRunningTest.name || undefined
1765             );
1766             if (_isGenerator(result)) {
1767               Assert.ok(false, "Task returned a generator");
1768             }
1769             _setTaskPrefs(initialPrefsValues);
1770             run_next_test();
1771           },
1772           ex => {
1773             _gTaskRunning = _gSetupRunning = false;
1774             ChromeUtils.addProfilerMarker(
1775               "task",
1776               { category: "Test", startTime },
1777               _gRunningTest.name || undefined
1778             );
1779             _setTaskPrefs(initialPrefsValues);
1780             try {
1781               // Note `ex` at this point could be undefined, for example as
1782               // result of a bare call to reject().
1783               do_report_unexpected_exception(ex);
1784             } catch (error) {
1785               // The above throws NS_ERROR_ABORT and we don't want this to show
1786               // up as an unhandled rejection later. If any other exception
1787               // happened, something went wrong, so we abort.
1788               if (error.result != Cr.NS_ERROR_ABORT) {
1789                 let extra = {};
1790                 if (error.fileName) {
1791                   extra.source_file = error.fileName;
1792                   if (error.lineNumber) {
1793                     extra.line_number = error.lineNumber;
1794                   }
1795                 } else {
1796                   extra.source_file = "xpcshell/head.js";
1797                 }
1798                 if (error.stack) {
1799                   extra.stack = _format_stack(error.stack);
1800                 }
1801                 _testLogger.error(_exception_message(error), extra);
1802                 _do_quit();
1803                 throw Components.Exception("", Cr.NS_ERROR_ABORT);
1804               }
1805             }
1806           }
1807         );
1808       } else {
1809         // Exceptions do not kill asynchronous tests, so they'll time out.
1810         let startTime = Cu.now();
1811         try {
1812           _gRunningTest();
1813         } catch (e) {
1814           do_throw(e);
1815         } finally {
1816           ChromeUtils.addProfilerMarker(
1817             "xpcshell-test",
1818             { category: "Test", startTime },
1819             _gRunningTest.name || undefined
1820           );
1821           _setTaskPrefs(initialPrefsValues);
1822         }
1823       }
1824     }
1825   }
1827   function frontLoadSetups() {
1828     _gTests.sort(([propsA, funcA], [propsB, funcB]) => {
1829       if (propsB.isSetup === propsA.isSetup) {
1830         return 0;
1831       }
1832       return propsB.isSetup ? 1 : -1;
1833     });
1834   }
1836   if (!_gTestIndex) {
1837     frontLoadSetups();
1838   }
1840   // For sane stacks during failures, we execute this code soon, but not now.
1841   // We do this now, before we call do_test_finished(), to ensure the pending
1842   // counter (_tests_pending) never reaches 0 while we still have tests to run
1843   // (executeSoon bumps that counter).
1844   executeSoon(_run_next_test, "run_next_test " + _gTestIndex);
1846   if (_gRunningTest !== null) {
1847     // Close the previous test do_test_pending call.
1848     do_test_finished(_gRunningTest.name);
1849   }
1852 try {
1853   // Set global preferences
1854   if (runningInParent) {
1855     let prefsFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1856     prefsFile.initWithPath(_PREFS_FILE);
1857     _Services.prefs.readUserPrefsFromFile(prefsFile);
1858   }
1859 } catch (e) {
1860   do_throw(e);
1864  * Changing/Adding scalars or events to Telemetry is supported in build-faster/artifacts builds.
1865  * These need to be loaded explicitly at start.
1866  * It usually happens once all of Telemetry is initialized and set up.
1867  * However in xpcshell tests Telemetry is not necessarily fully loaded,
1868  * so we help out users by loading at least the dynamic-builtin probes.
1869  */
1870 try {
1871   // We only need to run this in the parent process.
1872   // We only want to run this for local developer builds (which should have a "default" update channel).
1873   if (runningInParent && _AppConstants.MOZ_UPDATE_CHANNEL == "default") {
1874     let startTime = Cu.now();
1875     let { TelemetryController: _TelemetryController } =
1876       ChromeUtils.importESModule(
1877         "resource://gre/modules/TelemetryController.sys.mjs"
1878       );
1880     let complete = false;
1881     _TelemetryController.testRegisterJsProbes().finally(() => {
1882       ChromeUtils.addProfilerMarker(
1883         "xpcshell-test",
1884         { category: "Test", startTime },
1885         "TelemetryController.testRegisterJsProbes"
1886       );
1887       complete = true;
1888     });
1889     _Services.tm.spinEventLoopUntil(
1890       "Test(xpcshell/head.js:run_next-Test)",
1891       () => complete
1892     );
1893   }
1894 } catch (e) {
1895   do_throw(e);
1898 function _load_mozinfo() {
1899   let mozinfoFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1900   mozinfoFile.initWithPath(_MOZINFO_JS_PATH);
1901   let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
1902     Ci.nsIFileInputStream
1903   );
1904   stream.init(mozinfoFile, -1, 0, 0);
1905   let bytes = _NetUtil.readInputStream(stream, stream.available());
1906   let decoded = JSON.parse(new TextDecoder().decode(bytes));
1907   stream.close();
1908   return decoded;
1911 Object.defineProperty(this, "mozinfo", {
1912   configurable: true,
1913   get() {
1914     let _mozinfo = _load_mozinfo();
1915     Object.defineProperty(this, "mozinfo", {
1916       configurable: false,
1917       value: _mozinfo,
1918     });
1919     return _mozinfo;
1920   },