Bug 1842773 - Part 5: Add ArrayBuffer.prototype.{maxByteLength,resizable} getters...
[gecko.git] / testing / xpcshell / head.js
bloba487c481ef6b0c4e7941d8e91ba9fdaee3add3d3
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   let lastUpdate = Date.now();
492   tm.spinEventLoopUntil("Test(xpcshell/head.js:_initDebugging)", () => {
493     if (initialized) {
494       return true;
495     }
496     if (Date.now() - lastUpdate > 5000) {
497       info("Still waiting for debugger to connect...");
498       lastUpdate = Date.now();
499     }
500     return false;
501   });
502   // NOTE: if you want to debug the harness itself, you can now add a 'debugger'
503   // statement anywhere and it will stop - but we've already added a breakpoint
504   // for the first line of the test scripts, so we just continue...
505   info("Debugger connected, starting test execution");
508 function _execute_test() {
509   if (typeof _TEST_CWD != "undefined") {
510     try {
511       changeTestShellDir(_TEST_CWD);
512     } catch (e) {
513       _testLogger.error(_exception_message(e));
514     }
515   }
516   if (runningInParent && _AppConstants.platform == "android") {
517     try {
518       // GeckoView initialization needs the profile
519       do_get_profile(true);
520       // Wake up GeckoViewStartup
521       let geckoViewStartup = Cc["@mozilla.org/geckoview/startup;1"].getService(
522         Ci.nsIObserver
523       );
524       geckoViewStartup.observe(null, "profile-after-change", null);
525       geckoViewStartup.observe(null, "app-startup", null);
527       // Glean needs to be initialized for metric recording & tests to work.
528       // Usually this happens through Glean Kotlin,
529       // but for xpcshell tests we initialize it from here.
530       _Services.fog.initializeFOG();
531     } catch (ex) {
532       do_throw(`Failed to initialize GeckoView: ${ex}`, ex.stack);
533     }
534   }
536   // _JSDEBUGGER_PORT is dynamically defined by <runxpcshelltests.py>.
537   if (_JSDEBUGGER_PORT) {
538     try {
539       _initDebugging(_JSDEBUGGER_PORT);
540     } catch (ex) {
541       // Fail the test run immediately if debugging is requested but fails, so
542       // that the failure state is more obvious.
543       do_throw(`Failed to initialize debugging: ${ex}`, ex.stack);
544     }
545   }
547   _register_protocol_handlers();
549   // Override idle service by default.
550   // Call do_get_idle() to restore the factory and get the service.
551   _fakeIdleService.activate();
553   _PromiseTestUtils.init();
555   let coverageCollector = null;
556   if (typeof _JSCOV_DIR === "string") {
557     let _CoverageCollector = ChromeUtils.importESModule(
558       "resource://testing-common/CoverageUtils.sys.mjs"
559     ).CoverageCollector;
560     coverageCollector = new _CoverageCollector(_JSCOV_DIR);
561   }
563   let startTime = Cu.now();
565   // _HEAD_FILES is dynamically defined by <runxpcshelltests.py>.
566   _load_files(_HEAD_FILES);
567   // _TEST_FILE is dynamically defined by <runxpcshelltests.py>.
568   _load_files(_TEST_FILE);
570   // Tack Assert.sys.mjs methods to the current scope.
571   this.Assert = Assert;
572   for (let func in Assert) {
573     this[func] = Assert[func].bind(Assert);
574   }
576   const { PerTestCoverageUtils } = ChromeUtils.importESModule(
577     "resource://testing-common/PerTestCoverageUtils.sys.mjs"
578   );
580   if (runningInParent) {
581     PerTestCoverageUtils.beforeTestSync();
582   }
584   try {
585     do_test_pending("MAIN run_test");
586     // Check if run_test() is defined. If defined, run it.
587     // Else, call run_next_test() directly to invoke tests
588     // added by add_test() and add_task().
589     if (typeof run_test === "function") {
590       run_test();
591     } else {
592       run_next_test();
593     }
595     do_test_finished("MAIN run_test");
596     _do_main();
597     _PromiseTestUtils.assertNoUncaughtRejections();
599     if (coverageCollector != null) {
600       coverageCollector.recordTestCoverage(_TEST_FILE[0]);
601     }
603     if (runningInParent) {
604       PerTestCoverageUtils.afterTestSync();
605     }
606   } catch (e) {
607     _passed = false;
608     // do_check failures are already logged and set _quit to true and throw
609     // NS_ERROR_ABORT. If both of those are true it is likely this exception
610     // has already been logged so there is no need to log it again. It's
611     // possible that this will mask an NS_ERROR_ABORT that happens after a
612     // do_check failure though.
614     if (!_quit || e.result != Cr.NS_ERROR_ABORT) {
615       let extra = {};
616       if (e.fileName) {
617         extra.source_file = e.fileName;
618         if (e.lineNumber) {
619           extra.line_number = e.lineNumber;
620         }
621       } else {
622         extra.source_file = "xpcshell/head.js";
623       }
624       let message = _exception_message(e);
625       if (e.stack) {
626         extra.stack = _format_stack(e.stack);
627       }
628       _testLogger.error(message, extra);
629     }
630   } finally {
631     if (coverageCollector != null) {
632       coverageCollector.finalize();
633     }
634   }
636   // Execute all of our cleanup functions.
637   let reportCleanupError = function (ex) {
638     let stack, filename;
639     if (ex && typeof ex == "object" && "stack" in ex) {
640       stack = ex.stack;
641     } else {
642       stack = Components.stack.caller;
643     }
644     if (stack instanceof Ci.nsIStackFrame) {
645       filename = stack.filename;
646     } else if (ex.fileName) {
647       filename = ex.fileName;
648     }
649     _testLogger.error(_exception_message(ex), {
650       stack: _format_stack(stack),
651       source_file: filename,
652     });
653   };
655   let complete = !_cleanupFunctions.length;
656   let cleanupStartTime = complete ? 0 : Cu.now();
657   (async () => {
658     for (let func of _cleanupFunctions.reverse()) {
659       try {
660         let result = await func();
661         if (_isGenerator(result)) {
662           Assert.ok(false, "Cleanup function returned a generator");
663         }
664       } catch (ex) {
665         reportCleanupError(ex);
666       }
667     }
668     _cleanupFunctions = [];
669   })()
670     .catch(reportCleanupError)
671     .then(() => (complete = true));
672   _Services.tm.spinEventLoopUntil(
673     "Test(xpcshell/head.js:_execute_test)",
674     () => complete
675   );
676   if (cleanupStartTime) {
677     ChromeUtils.addProfilerMarker(
678       "xpcshell-test",
679       { category: "Test", startTime: cleanupStartTime },
680       "Cleanup functions"
681     );
682   }
684   ChromeUtils.addProfilerMarker(
685     "xpcshell-test",
686     { category: "Test", startTime },
687     _TEST_NAME
688   );
689   _Services.obs.notifyObservers(null, "test-complete");
691   // Restore idle service to avoid leaks.
692   _fakeIdleService.deactivate();
694   if (
695     globalThis.hasOwnProperty("storage") &&
696     StorageManager.isInstance(globalThis.storage)
697   ) {
698     globalThis.storage.shutdown();
699   }
701   if (_profileInitialized) {
702     // Since we have a profile, we will notify profile shutdown topics at
703     // the end of the current test, to ensure correct cleanup on shutdown.
704     _Services.startup.advanceShutdownPhase(
705       _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNNETTEARDOWN
706     );
707     _Services.startup.advanceShutdownPhase(
708       _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNTEARDOWN
709     );
710     _Services.startup.advanceShutdownPhase(
711       _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWN
712     );
713     _Services.startup.advanceShutdownPhase(
714       _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNQM
715     );
717     _profileInitialized = false;
718   }
720   try {
721     _PromiseTestUtils.ensureDOMPromiseRejectionsProcessed();
722     _PromiseTestUtils.assertNoUncaughtRejections();
723     _PromiseTestUtils.assertNoMoreExpectedRejections();
724   } finally {
725     // It's important to terminate the module to avoid crashes on shutdown.
726     _PromiseTestUtils.uninit();
727   }
729   // Skip the normal shutdown path for optimized builds that don't do leak checking.
730   if (
731     runningInParent &&
732     !_AppConstants.RELEASE_OR_BETA &&
733     !_AppConstants.DEBUG &&
734     !_AppConstants.MOZ_CODE_COVERAGE &&
735     !_AppConstants.ASAN &&
736     !_AppConstants.TSAN
737   ) {
738     Cu.exitIfInAutomation();
739   }
743  * Loads files.
745  * @param aFiles Array of files to load.
746  */
747 function _load_files(aFiles) {
748   function load_file(element, index, array) {
749     try {
750       let startTime = Cu.now();
751       load(element);
752       ChromeUtils.addProfilerMarker(
753         "load_file",
754         { category: "Test", startTime },
755         element.replace(/.*\/_?tests\/xpcshell\//, "")
756       );
757     } catch (e) {
758       let extra = {
759         source_file: element,
760       };
761       if (e.stack) {
762         extra.stack = _format_stack(e.stack);
763       }
764       _testLogger.error(_exception_message(e), extra);
765     }
766   }
768   aFiles.forEach(load_file);
771 function _wrap_with_quotes_if_necessary(val) {
772   return typeof val == "string" ? '"' + val + '"' : val;
775 /* ************* Functions to be used from the tests ************* */
778  * Prints a message to the output log.
779  */
780 function info(msg, data) {
781   ChromeUtils.addProfilerMarker("INFO", { category: "Test" }, msg);
782   msg = _wrap_with_quotes_if_necessary(msg);
783   data = data ? data : null;
784   _testLogger.info(msg, data);
788  * Calls the given function at least the specified number of milliseconds later.
789  * The callback will not undershoot the given time, but it might overshoot --
790  * don't expect precision!
792  * @param delay : uint
793  *   the number of milliseconds to delay
794  * @param callback : function() : void
795  *   the function to call
796  */
797 function do_timeout(delay, func) {
798   new _Timer(func, Number(delay));
801 function executeSoon(callback, aName) {
802   let funcName = aName ? aName : callback.name;
803   do_test_pending(funcName);
805   _Services.tm.dispatchToMainThread({
806     run() {
807       try {
808         callback();
809       } catch (e) {
810         // do_check failures are already logged and set _quit to true and throw
811         // NS_ERROR_ABORT. If both of those are true it is likely this exception
812         // has already been logged so there is no need to log it again. It's
813         // possible that this will mask an NS_ERROR_ABORT that happens after a
814         // do_check failure though.
815         if (!_quit || e.result != Cr.NS_ERROR_ABORT) {
816           let stack = e.stack ? _format_stack(e.stack) : null;
817           _testLogger.testStatus(
818             _TEST_NAME,
819             funcName,
820             "FAIL",
821             "PASS",
822             _exception_message(e),
823             stack
824           );
825           _do_quit();
826         }
827       } finally {
828         do_test_finished(funcName);
829       }
830     },
831   });
835  * Shows an error message and the current stack and aborts the test.
837  * @param error  A message string or an Error object.
838  * @param stack  null or nsIStackFrame object or a string containing
839  *               \n separated stack lines (as in Error().stack).
840  */
841 function do_throw(error, stack) {
842   let filename = "";
843   // If we didn't get passed a stack, maybe the error has one
844   // otherwise get it from our call context
845   stack = stack || error.stack || Components.stack.caller;
847   if (stack instanceof Ci.nsIStackFrame) {
848     filename = stack.filename;
849   } else if (error.fileName) {
850     filename = error.fileName;
851   }
853   _testLogger.error(_exception_message(error), {
854     source_file: filename,
855     stack: _format_stack(stack),
856   });
857   ChromeUtils.addProfilerMarker(
858     "ERROR",
859     { category: "Test", captureStack: true },
860     _exception_message(error)
861   );
862   _abort_failed_test();
865 function _abort_failed_test() {
866   // Called to abort the test run after all failures are logged.
867   _passed = false;
868   _do_quit();
869   throw Components.Exception("", Cr.NS_ERROR_ABORT);
872 function _format_stack(stack) {
873   let normalized;
874   if (stack instanceof Ci.nsIStackFrame) {
875     let frames = [];
876     for (let frame = stack; frame; frame = frame.caller) {
877       frames.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
878     }
879     normalized = frames.join("\n");
880   } else {
881     normalized = "" + stack;
882   }
883   return normalized;
886 // Make a nice display string from an object that behaves
887 // like Error
888 function _exception_message(ex) {
889   if (ex === undefined) {
890     return "`undefined` exception, maybe from an empty reject()?";
891   }
892   let message = "";
893   if (ex.name) {
894     message = ex.name + ": ";
895   }
896   if (ex.message) {
897     message += ex.message;
898   }
899   if (ex.fileName) {
900     message += " at " + ex.fileName;
901     if (ex.lineNumber) {
902       message += ":" + ex.lineNumber;
903     }
904   }
905   if (message !== "") {
906     return message;
907   }
908   // Force ex to be stringified
909   return "" + ex;
912 function do_report_unexpected_exception(ex, text) {
913   let filename = Components.stack.caller.filename;
914   text = text ? text + " - " : "";
916   _passed = false;
917   _testLogger.error(text + "Unexpected exception " + _exception_message(ex), {
918     source_file: filename,
919     stack: _format_stack(ex?.stack),
920   });
921   _do_quit();
922   throw Components.Exception("", Cr.NS_ERROR_ABORT);
925 function do_note_exception(ex, text) {
926   let filename = Components.stack.caller.filename;
927   _testLogger.info(text + "Swallowed exception " + _exception_message(ex), {
928     source_file: filename,
929     stack: _format_stack(ex?.stack),
930   });
933 function do_report_result(passed, text, stack, todo) {
934   // Match names like head.js, head_foo.js, and foo_head.js, but not
935   // test_headache.js
936   while (/(\/head(_.+)?|head)\.js$/.test(stack.filename) && stack.caller) {
937     stack = stack.caller;
938   }
940   let name = _gRunningTest ? _gRunningTest.name : stack.name;
941   let message;
942   if (name) {
943     message = "[" + name + " : " + stack.lineNumber + "] " + text;
944   } else {
945     message = text;
946   }
948   if (passed) {
949     if (todo) {
950       _testLogger.testStatus(
951         _TEST_NAME,
952         name,
953         "PASS",
954         "FAIL",
955         message,
956         _format_stack(stack)
957       );
958       ChromeUtils.addProfilerMarker(
959         "UNEXPECTED-PASS",
960         { category: "Test" },
961         message
962       );
963       _abort_failed_test();
964     } else {
965       _testLogger.testStatus(_TEST_NAME, name, "PASS", "PASS", message);
966       ChromeUtils.addProfilerMarker("PASS", { category: "Test" }, message);
967     }
968   } else if (todo) {
969     _testLogger.testStatus(_TEST_NAME, name, "FAIL", "FAIL", message);
970     ChromeUtils.addProfilerMarker("TODO", { category: "Test" }, message);
971   } else {
972     _testLogger.testStatus(
973       _TEST_NAME,
974       name,
975       "FAIL",
976       "PASS",
977       message,
978       _format_stack(stack)
979     );
980     ChromeUtils.addProfilerMarker("FAIL", { category: "Test" }, message);
981     _abort_failed_test();
982   }
985 function _do_check_eq(left, right, stack, todo) {
986   if (!stack) {
987     stack = Components.stack.caller;
988   }
990   var text =
991     _wrap_with_quotes_if_necessary(left) +
992     " == " +
993     _wrap_with_quotes_if_necessary(right);
994   do_report_result(left == right, text, stack, todo);
997 function todo_check_eq(left, right, stack) {
998   if (!stack) {
999     stack = Components.stack.caller;
1000   }
1002   _do_check_eq(left, right, stack, true);
1005 function todo_check_true(condition, stack) {
1006   if (!stack) {
1007     stack = Components.stack.caller;
1008   }
1010   todo_check_eq(condition, true, stack);
1013 function todo_check_false(condition, stack) {
1014   if (!stack) {
1015     stack = Components.stack.caller;
1016   }
1018   todo_check_eq(condition, false, stack);
1021 function todo_check_null(condition, stack = Components.stack.caller) {
1022   todo_check_eq(condition, null, stack);
1025 // Check that |func| throws an nsIException that has
1026 // |Components.results[resultName]| as the value of its 'result' property.
1027 function do_check_throws_nsIException(
1028   func,
1029   resultName,
1030   stack = Components.stack.caller,
1031   todo = false
1032 ) {
1033   let expected = Cr[resultName];
1034   if (typeof expected !== "number") {
1035     do_throw(
1036       "do_check_throws_nsIException requires a Components.results" +
1037         " property name, not " +
1038         uneval(resultName),
1039       stack
1040     );
1041   }
1043   let msg =
1044     "do_check_throws_nsIException: func should throw" +
1045     " an nsIException whose 'result' is Components.results." +
1046     resultName;
1048   try {
1049     func();
1050   } catch (ex) {
1051     if (!(ex instanceof Ci.nsIException) || ex.result !== expected) {
1052       do_report_result(
1053         false,
1054         msg + ", threw " + legible_exception(ex) + " instead",
1055         stack,
1056         todo
1057       );
1058     }
1060     do_report_result(true, msg, stack, todo);
1061     return;
1062   }
1064   // Call this here, not in the 'try' clause, so do_report_result's own
1065   // throw doesn't get caught by our 'catch' clause.
1066   do_report_result(false, msg + ", but returned normally", stack, todo);
1069 // Produce a human-readable form of |exception|. This looks up
1070 // Components.results values, tries toString methods, and so on.
1071 function legible_exception(exception) {
1072   switch (typeof exception) {
1073     case "object":
1074       if (exception instanceof Ci.nsIException) {
1075         return "nsIException instance: " + uneval(exception.toString());
1076       }
1077       return exception.toString();
1079     case "number":
1080       for (let name in Cr) {
1081         if (exception === Cr[name]) {
1082           return "Components.results." + name;
1083         }
1084       }
1086     // Fall through.
1087     default:
1088       return uneval(exception);
1089   }
1092 function do_check_instanceof(
1093   value,
1094   constructor,
1095   stack = Components.stack.caller,
1096   todo = false
1097 ) {
1098   do_report_result(
1099     value instanceof constructor,
1100     "value should be an instance of " + constructor.name,
1101     stack,
1102     todo
1103   );
1106 function todo_check_instanceof(
1107   value,
1108   constructor,
1109   stack = Components.stack.caller
1110 ) {
1111   do_check_instanceof(value, constructor, stack, true);
1114 function do_test_pending(aName) {
1115   ++_tests_pending;
1117   _testLogger.info(
1118     "(xpcshell/head.js) | test" +
1119       (aName ? " " + aName : "") +
1120       " pending (" +
1121       _tests_pending +
1122       ")"
1123   );
1126 function do_test_finished(aName) {
1127   _testLogger.info(
1128     "(xpcshell/head.js) | test" +
1129       (aName ? " " + aName : "") +
1130       " finished (" +
1131       _tests_pending +
1132       ")"
1133   );
1134   if (--_tests_pending == 0) {
1135     _do_quit();
1136   }
1139 function do_get_file(path, allowNonexistent) {
1140   try {
1141     let lf = _Services.dirsvc.get("CurWorkD", Ci.nsIFile);
1143     let bits = path.split("/");
1144     for (let i = 0; i < bits.length; i++) {
1145       if (bits[i]) {
1146         if (bits[i] == "..") {
1147           lf = lf.parent;
1148         } else {
1149           lf.append(bits[i]);
1150         }
1151       }
1152     }
1154     if (!allowNonexistent && !lf.exists()) {
1155       // Not using do_throw(): caller will continue.
1156       _passed = false;
1157       var stack = Components.stack.caller;
1158       _testLogger.error(
1159         "[" +
1160           stack.name +
1161           " : " +
1162           stack.lineNumber +
1163           "] " +
1164           lf.path +
1165           " does not exist"
1166       );
1167     }
1169     return lf;
1170   } catch (ex) {
1171     do_throw(ex.toString(), Components.stack.caller);
1172   }
1174   return null;
1177 // do_get_cwd() isn't exactly self-explanatory, so provide a helper
1178 function do_get_cwd() {
1179   return do_get_file("");
1182 function do_load_manifest(path) {
1183   var lf = do_get_file(path);
1184   const nsIComponentRegistrar = Ci.nsIComponentRegistrar;
1185   Assert.ok(Components.manager instanceof nsIComponentRegistrar);
1186   // Previous do_check_true() is not a test check.
1187   Components.manager.autoRegister(lf);
1191  * Parse a DOM document.
1193  * @param aPath File path to the document.
1194  * @param aType Content type to use in DOMParser.
1196  * @return Document from the file.
1197  */
1198 function do_parse_document(aPath, aType) {
1199   switch (aType) {
1200     case "application/xhtml+xml":
1201     case "application/xml":
1202     case "text/xml":
1203       break;
1205     default:
1206       do_throw(
1207         "type: expected application/xhtml+xml, application/xml or text/xml," +
1208           " got '" +
1209           aType +
1210           "'",
1211         Components.stack.caller
1212       );
1213   }
1215   let file = do_get_file(aPath);
1216   let url = _Services.io.newFileURI(file).spec;
1217   file = null;
1218   return new Promise((resolve, reject) => {
1219     let xhr = new XMLHttpRequest();
1220     xhr.open("GET", url);
1221     xhr.responseType = "document";
1222     xhr.onerror = reject;
1223     xhr.onload = () => {
1224       resolve(xhr.response);
1225     };
1226     xhr.send();
1227   });
1231  * Registers a function that will run when the test harness is done running all
1232  * tests.
1234  * @param aFunction
1235  *        The function to be called when the test harness has finished running.
1236  */
1237 function registerCleanupFunction(aFunction) {
1238   _cleanupFunctions.push(aFunction);
1242  * Returns the directory for a temp dir, which is created by the
1243  * test harness. Every test gets its own temp dir.
1245  * @return nsIFile of the temporary directory
1246  */
1247 function do_get_tempdir() {
1248   // the python harness sets this in the environment for us
1249   let path = _Services.env.get("XPCSHELL_TEST_TEMP_DIR");
1250   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1251   file.initWithPath(path);
1252   return file;
1256  * Returns the directory for crashreporter minidumps.
1258  * @return nsIFile of the minidump directory
1259  */
1260 function do_get_minidumpdir() {
1261   // the python harness may set this in the environment for us
1262   let path = _Services.env.get("XPCSHELL_MINIDUMP_DIR");
1263   if (path) {
1264     let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1265     file.initWithPath(path);
1266     return file;
1267   }
1268   return do_get_tempdir();
1272  * Registers a directory with the profile service,
1273  * and return the directory as an nsIFile.
1275  * @param notifyProfileAfterChange Whether to notify for "profile-after-change".
1276  * @return nsIFile of the profile directory.
1277  */
1278 function do_get_profile(notifyProfileAfterChange = false) {
1279   if (!runningInParent) {
1280     _testLogger.info("Ignoring profile creation from child process.");
1281     return null;
1282   }
1284   // the python harness sets this in the environment for us
1285   let profd = Services.env.get("XPCSHELL_TEST_PROFILE_DIR");
1286   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1287   file.initWithPath(profd);
1289   let provider = {
1290     getFile(prop, persistent) {
1291       persistent.value = true;
1292       if (
1293         prop == "ProfD" ||
1294         prop == "ProfLD" ||
1295         prop == "ProfDS" ||
1296         prop == "ProfLDS" ||
1297         prop == "TmpD"
1298       ) {
1299         return file.clone();
1300       }
1301       return null;
1302     },
1303     QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]),
1304   };
1305   _Services.dirsvc
1306     .QueryInterface(Ci.nsIDirectoryService)
1307     .registerProvider(provider);
1309   try {
1310     _Services.dirsvc.undefine("TmpD");
1311   } catch (e) {
1312     // This throws if the key is not already registered, but that
1313     // doesn't matter.
1314     if (e.result != Cr.NS_ERROR_FAILURE) {
1315       throw e;
1316     }
1317   }
1319   // We need to update the crash events directory when the profile changes.
1320   if (runningInParent && "@mozilla.org/toolkit/crash-reporter;1" in Cc) {
1321     // Intentially access the crash reporter service directly for this.
1322     // eslint-disable-next-line mozilla/use-services
1323     let crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService(
1324       Ci.nsICrashReporter
1325     );
1326     crashReporter.UpdateCrashEventsDir();
1327   }
1329   if (!_profileInitialized) {
1330     _Services.obs.notifyObservers(
1331       null,
1332       "profile-do-change",
1333       "xpcshell-do-get-profile"
1334     );
1335     _profileInitialized = true;
1336     if (notifyProfileAfterChange) {
1337       _Services.obs.notifyObservers(
1338         null,
1339         "profile-after-change",
1340         "xpcshell-do-get-profile"
1341       );
1342     }
1343   }
1345   // The methods of 'provider' will retain this scope so null out everything
1346   // to avoid spurious leak reports.
1347   profd = null;
1348   provider = null;
1349   return file.clone();
1353  * This function loads head.js (this file) in the child process, so that all
1354  * functions defined in this file (do_throw, etc) are available to subsequent
1355  * sendCommand calls.  It also sets various constants used by these functions.
1357  * (Note that you may use sendCommand without calling this function first;  you
1358  * simply won't have any of the functions in this file available.)
1359  */
1360 function do_load_child_test_harness() {
1361   // Make sure this isn't called from child process
1362   if (!runningInParent) {
1363     do_throw("run_test_in_child cannot be called from child!");
1364   }
1366   // Allow to be called multiple times, but only run once
1367   if (typeof do_load_child_test_harness.alreadyRun != "undefined") {
1368     return;
1369   }
1370   do_load_child_test_harness.alreadyRun = 1;
1372   _XPCSHELL_PROCESS = "parent";
1374   let command =
1375     "const _HEAD_JS_PATH=" +
1376     uneval(_HEAD_JS_PATH) +
1377     "; " +
1378     "const _HEAD_FILES=" +
1379     uneval(_HEAD_FILES) +
1380     "; " +
1381     "const _MOZINFO_JS_PATH=" +
1382     uneval(_MOZINFO_JS_PATH) +
1383     "; " +
1384     "const _TEST_NAME=" +
1385     uneval(_TEST_NAME) +
1386     "; " +
1387     // We'll need more magic to get the debugger working in the child
1388     "const _JSDEBUGGER_PORT=0; " +
1389     "_XPCSHELL_PROCESS='child';";
1391   if (typeof _JSCOV_DIR === "string") {
1392     command += " const _JSCOV_DIR=" + uneval(_JSCOV_DIR) + ";";
1393   }
1395   if (typeof _TEST_CWD != "undefined") {
1396     command += " const _TEST_CWD=" + uneval(_TEST_CWD) + ";";
1397   }
1399   if (_TESTING_MODULES_DIR) {
1400     command +=
1401       " const _TESTING_MODULES_DIR=" + uneval(_TESTING_MODULES_DIR) + ";";
1402   }
1404   command += " load(_HEAD_JS_PATH);";
1405   sendCommand(command);
1409  * Runs an entire xpcshell unit test in a child process (rather than in chrome,
1410  * which is the default).
1412  * This function returns immediately, before the test has completed.
1414  * @param testFile
1415  *        The name of the script to run.  Path format same as load().
1416  * @param optionalCallback.
1417  *        Optional function to be called (in parent) when test on child is
1418  *        complete.  If provided, the function must call do_test_finished();
1419  * @return Promise Resolved when the test in the child is complete.
1420  */
1421 function run_test_in_child(testFile, optionalCallback) {
1422   return new Promise(resolve => {
1423     var callback = () => {
1424       resolve();
1425       if (typeof optionalCallback == "undefined") {
1426         do_test_finished();
1427       } else {
1428         optionalCallback();
1429       }
1430     };
1432     do_load_child_test_harness();
1434     var testPath = do_get_file(testFile).path.replace(/\\/g, "/");
1435     do_test_pending("run in child");
1436     sendCommand(
1437       "_testLogger.info('CHILD-TEST-STARTED'); " +
1438         "const _TEST_FILE=['" +
1439         testPath +
1440         "']; " +
1441         "_execute_test(); " +
1442         "_testLogger.info('CHILD-TEST-COMPLETED');",
1443       callback
1444     );
1445   });
1449  * Execute a given function as soon as a particular cross-process message is received.
1450  * Must be paired with do_send_remote_message or equivalent ProcessMessageManager calls.
1452  * @param optionalCallback
1453  *        Optional callback that is invoked when the message is received.  If provided,
1454  *        the function must call do_test_finished().
1455  * @return Promise Promise that is resolved when the message is received.
1456  */
1457 function do_await_remote_message(name, optionalCallback) {
1458   return new Promise(resolve => {
1459     var listener = {
1460       receiveMessage(message) {
1461         if (message.name == name) {
1462           mm.removeMessageListener(name, listener);
1463           resolve(message.data);
1464           if (optionalCallback) {
1465             optionalCallback(message.data);
1466           } else {
1467             do_test_finished();
1468           }
1469         }
1470       },
1471     };
1473     var mm;
1474     if (runningInParent) {
1475       mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService();
1476     } else {
1477       mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService();
1478     }
1479     do_test_pending();
1480     mm.addMessageListener(name, listener);
1481   });
1485  * Asynchronously send a message to all remote processes. Pairs with do_await_remote_message
1486  * or equivalent ProcessMessageManager listeners.
1487  */
1488 function do_send_remote_message(name, data) {
1489   var mm;
1490   var sender;
1491   if (runningInParent) {
1492     mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService();
1493     sender = "broadcastAsyncMessage";
1494   } else {
1495     mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService();
1496     sender = "sendAsyncMessage";
1497   }
1498   mm[sender](name, data);
1502  * Schedules and awaits a precise GC, and forces CC, `maxCount` number of times.
1503  * @param maxCount
1504  *        How many times GC and CC should be scheduled.
1505  */
1506 async function schedulePreciseGCAndForceCC(maxCount) {
1507   for (let count = 0; count < maxCount; count++) {
1508     await new Promise(resolve => Cu.schedulePreciseGC(resolve));
1509     Cu.forceCC();
1510   }
1514  * Add a test function to the list of tests that are to be run asynchronously.
1516  * @param funcOrProperties
1517  *        A function to be run or an object represents test properties.
1518  *        Supported properties:
1519  *          skip_if : An arrow function which has an expression to be
1520  *                    evaluated whether the test is skipped or not.
1521  *          pref_set: An array of preferences to set for the test, reset at end of test.
1522  * @param func
1523  *        A function to be run only if the funcOrProperies is not a function.
1524  * @param isTask
1525  *        Optional flag that indicates whether `func` is a task. Defaults to `false`.
1526  * @param isSetup
1527  *        Optional flag that indicates whether `func` is a setup task. Defaults to `false`.
1528  *        Implies isTask.
1530  * Each test function must call run_next_test() when it's done. Test files
1531  * should call run_next_test() in their run_test function to execute all
1532  * async tests.
1534  * @return the test function that was passed in.
1535  */
1536 var _gSupportedProperties = ["skip_if", "pref_set"];
1537 var _gTests = [];
1538 var _gRunOnlyThisTest = null;
1539 function add_test(
1540   properties,
1541   func = properties,
1542   isTask = false,
1543   isSetup = false
1544 ) {
1545   if (isSetup) {
1546     isTask = true;
1547   }
1548   if (typeof properties == "function") {
1549     properties = { isTask, isSetup };
1550     _gTests.push([properties, func]);
1551   } else if (typeof properties == "object") {
1552     // Ensure only documented properties are in the object.
1553     for (let prop of Object.keys(properties)) {
1554       if (!_gSupportedProperties.includes(prop)) {
1555         do_throw(`Task property is not supported: ${prop}`);
1556       }
1557     }
1558     properties.isTask = isTask;
1559     properties.isSetup = isSetup;
1560     _gTests.push([properties, func]);
1561   } else {
1562     do_throw("add_test() should take a function or an object and a function");
1563   }
1564   func.skip = () => (properties.skip_if = () => true);
1565   func.only = () => (_gRunOnlyThisTest = func);
1566   return func;
1570  * Add a test function which is a Task function.
1572  * @param funcOrProperties
1573  *        An async function to be run or an object represents test properties.
1574  *        Supported properties:
1575  *          skip_if : An arrow function which has an expression to be
1576  *                    evaluated whether the test is skipped or not.
1577  *          pref_set: An array of preferences to set for the test, reset at end of test.
1578  * @param func
1579  *        An async function to be run only if the funcOrProperies is not a function.
1581  * Task functions are functions fed into Task.jsm's Task.spawn(). They are async
1582  * functions that emit promises.
1584  * If an exception is thrown, a do_check_* comparison fails, or if a rejected
1585  * promise is yielded, the test function aborts immediately and the test is
1586  * reported as a failure.
1588  * Unlike add_test(), there is no need to call run_next_test(). The next test
1589  * will run automatically as soon the task function is exhausted. To trigger
1590  * premature (but successful) termination of the function or simply return.
1592  * Example usage:
1594  * add_task(async function test() {
1595  *   let result = await Promise.resolve(true);
1597  *   do_check_true(result);
1599  *   let secondary = await someFunctionThatReturnsAPromise(result);
1600  *   do_check_eq(secondary, "expected value");
1601  * });
1603  * add_task(async function test_early_return() {
1604  *   let result = await somethingThatReturnsAPromise();
1606  *   if (!result) {
1607  *     // Test is ended immediately, with success.
1608  *     return;
1609  *   }
1611  *   do_check_eq(result, "foo");
1612  * });
1614  * add_task({
1615  *   skip_if: () => !("@mozilla.org/telephony/volume-service;1" in Components.classes),
1616  *   pref_set: [["some.pref", "value"], ["another.pref", true]],
1617  * }, async function test_volume_service() {
1618  *   let volumeService = Cc["@mozilla.org/telephony/volume-service;1"]
1619  *     .getService(Ci.nsIVolumeService);
1620  *   ...
1621  * });
1622  */
1623 function add_task(properties, func = properties) {
1624   return add_test(properties, func, true);
1628  * add_setup is like add_task, but creates setup tasks.
1629  */
1630 function add_setup(properties, func = properties) {
1631   return add_test(properties, func, true, true);
1634 const _setTaskPrefs = prefs => {
1635   for (let [pref, value] of prefs) {
1636     if (value === undefined) {
1637       // Clear any pref that didn't have a user value.
1638       info(`Clearing pref "${pref}"`);
1639       _Services.prefs.clearUserPref(pref);
1640       continue;
1641     }
1643     info(`Setting pref "${pref}": ${value}`);
1644     switch (typeof value) {
1645       case "boolean":
1646         _Services.prefs.setBoolPref(pref, value);
1647         break;
1648       case "number":
1649         _Services.prefs.setIntPref(pref, value);
1650         break;
1651       case "string":
1652         _Services.prefs.setStringPref(pref, value);
1653         break;
1654       default:
1655         throw new Error("runWithPrefs doesn't support this pref type yet");
1656     }
1657   }
1660 const _getTaskPrefs = prefs => {
1661   return prefs.map(([pref, value]) => {
1662     info(`Getting initial pref value for "${pref}"`);
1663     if (!_Services.prefs.prefHasUserValue(pref)) {
1664       // Check if the pref doesn't have a user value.
1665       return [pref, undefined];
1666     }
1667     switch (typeof value) {
1668       case "boolean":
1669         return [pref, _Services.prefs.getBoolPref(pref)];
1670       case "number":
1671         return [pref, _Services.prefs.getIntPref(pref)];
1672       case "string":
1673         return [pref, _Services.prefs.getStringPref(pref)];
1674       default:
1675         throw new Error("runWithPrefs doesn't support this pref type yet");
1676     }
1677   });
1681  * Runs the next test function from the list of async tests.
1682  */
1683 var _gRunningTest = null;
1684 var _gTestIndex = 0; // The index of the currently running test.
1685 var _gTaskRunning = false;
1686 var _gSetupRunning = false;
1687 function run_next_test() {
1688   if (_gTaskRunning) {
1689     throw new Error(
1690       "run_next_test() called from an add_task() test function. " +
1691         "run_next_test() should not be called from inside add_setup() or add_task() " +
1692         "under any circumstances!"
1693     );
1694   }
1696   if (_gSetupRunning) {
1697     throw new Error(
1698       "run_next_test() called from an add_setup() test function. " +
1699         "run_next_test() should not be called from inside add_setup() or add_task() " +
1700         "under any circumstances!"
1701     );
1702   }
1704   function _run_next_test() {
1705     if (_gTestIndex < _gTests.length) {
1706       // Check for uncaught rejections as early and often as possible.
1707       _PromiseTestUtils.assertNoUncaughtRejections();
1708       let _properties;
1709       [_properties, _gRunningTest] = _gTests[_gTestIndex++];
1711       // Must set to pending before we check for skip, so that we keep the
1712       // running counts correct.
1713       _testLogger.info(
1714         `${_TEST_NAME} | Starting ${_properties.isSetup ? "setup " : ""}${
1715           _gRunningTest.name
1716         }`
1717       );
1718       do_test_pending(_gRunningTest.name);
1720       if (
1721         (typeof _properties.skip_if == "function" && _properties.skip_if()) ||
1722         (_gRunOnlyThisTest &&
1723           _gRunningTest != _gRunOnlyThisTest &&
1724           !_properties.isSetup)
1725       ) {
1726         let _condition = _gRunOnlyThisTest
1727           ? "only one task may run."
1728           : _properties.skip_if.toSource().replace(/\(\)\s*=>\s*/, "");
1729         if (_condition == "true") {
1730           _condition = "explicitly skipped.";
1731         }
1732         let _message =
1733           _gRunningTest.name +
1734           " skipped because the following conditions were" +
1735           " met: (" +
1736           _condition +
1737           ")";
1738         _testLogger.testStatus(
1739           _TEST_NAME,
1740           _gRunningTest.name,
1741           "SKIP",
1742           "SKIP",
1743           _message
1744         );
1745         executeSoon(run_next_test);
1746         return;
1747       }
1749       let initialPrefsValues = [];
1750       if (_properties.pref_set) {
1751         initialPrefsValues = _getTaskPrefs(_properties.pref_set);
1752         _setTaskPrefs(_properties.pref_set);
1753       }
1755       if (_properties.isTask) {
1756         if (_properties.isSetup) {
1757           _gSetupRunning = true;
1758         } else {
1759           _gTaskRunning = true;
1760         }
1761         let startTime = Cu.now();
1762         (async () => _gRunningTest())().then(
1763           result => {
1764             _gTaskRunning = _gSetupRunning = false;
1765             ChromeUtils.addProfilerMarker(
1766               "task",
1767               { category: "Test", startTime },
1768               _gRunningTest.name || undefined
1769             );
1770             if (_isGenerator(result)) {
1771               Assert.ok(false, "Task returned a generator");
1772             }
1773             _setTaskPrefs(initialPrefsValues);
1774             run_next_test();
1775           },
1776           ex => {
1777             _gTaskRunning = _gSetupRunning = false;
1778             ChromeUtils.addProfilerMarker(
1779               "task",
1780               { category: "Test", startTime },
1781               _gRunningTest.name || undefined
1782             );
1783             _setTaskPrefs(initialPrefsValues);
1784             try {
1785               // Note `ex` at this point could be undefined, for example as
1786               // result of a bare call to reject().
1787               do_report_unexpected_exception(ex);
1788             } catch (error) {
1789               // The above throws NS_ERROR_ABORT and we don't want this to show
1790               // up as an unhandled rejection later. If any other exception
1791               // happened, something went wrong, so we abort.
1792               if (error.result != Cr.NS_ERROR_ABORT) {
1793                 let extra = {};
1794                 if (error.fileName) {
1795                   extra.source_file = error.fileName;
1796                   if (error.lineNumber) {
1797                     extra.line_number = error.lineNumber;
1798                   }
1799                 } else {
1800                   extra.source_file = "xpcshell/head.js";
1801                 }
1802                 if (error.stack) {
1803                   extra.stack = _format_stack(error.stack);
1804                 }
1805                 _testLogger.error(_exception_message(error), extra);
1806                 _do_quit();
1807                 throw Components.Exception("", Cr.NS_ERROR_ABORT);
1808               }
1809             }
1810           }
1811         );
1812       } else {
1813         // Exceptions do not kill asynchronous tests, so they'll time out.
1814         let startTime = Cu.now();
1815         try {
1816           _gRunningTest();
1817         } catch (e) {
1818           do_throw(e);
1819         } finally {
1820           ChromeUtils.addProfilerMarker(
1821             "xpcshell-test",
1822             { category: "Test", startTime },
1823             _gRunningTest.name || undefined
1824           );
1825           _setTaskPrefs(initialPrefsValues);
1826         }
1827       }
1828     }
1829   }
1831   function frontLoadSetups() {
1832     _gTests.sort(([propsA, funcA], [propsB, funcB]) => {
1833       if (propsB.isSetup === propsA.isSetup) {
1834         return 0;
1835       }
1836       return propsB.isSetup ? 1 : -1;
1837     });
1838   }
1840   if (!_gTestIndex) {
1841     frontLoadSetups();
1842   }
1844   // For sane stacks during failures, we execute this code soon, but not now.
1845   // We do this now, before we call do_test_finished(), to ensure the pending
1846   // counter (_tests_pending) never reaches 0 while we still have tests to run
1847   // (executeSoon bumps that counter).
1848   executeSoon(_run_next_test, "run_next_test " + _gTestIndex);
1850   if (_gRunningTest !== null) {
1851     // Close the previous test do_test_pending call.
1852     do_test_finished(_gRunningTest.name);
1853   }
1856 try {
1857   // Set global preferences
1858   if (runningInParent) {
1859     let prefsFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1860     prefsFile.initWithPath(_PREFS_FILE);
1861     _Services.prefs.readUserPrefsFromFile(prefsFile);
1862   }
1863 } catch (e) {
1864   do_throw(e);
1868  * Changing/Adding scalars or events to Telemetry is supported in build-faster/artifacts builds.
1869  * These need to be loaded explicitly at start.
1870  * It usually happens once all of Telemetry is initialized and set up.
1871  * However in xpcshell tests Telemetry is not necessarily fully loaded,
1872  * so we help out users by loading at least the dynamic-builtin probes.
1873  */
1874 try {
1875   // We only need to run this in the parent process.
1876   // We only want to run this for local developer builds (which should have a "default" update channel).
1877   if (runningInParent && _AppConstants.MOZ_UPDATE_CHANNEL == "default") {
1878     let startTime = Cu.now();
1879     let { TelemetryController: _TelemetryController } =
1880       ChromeUtils.importESModule(
1881         "resource://gre/modules/TelemetryController.sys.mjs"
1882       );
1884     let complete = false;
1885     _TelemetryController.testRegisterJsProbes().finally(() => {
1886       ChromeUtils.addProfilerMarker(
1887         "xpcshell-test",
1888         { category: "Test", startTime },
1889         "TelemetryController.testRegisterJsProbes"
1890       );
1891       complete = true;
1892     });
1893     _Services.tm.spinEventLoopUntil(
1894       "Test(xpcshell/head.js:run_next-Test)",
1895       () => complete
1896     );
1897   }
1898 } catch (e) {
1899   do_throw(e);
1902 function _load_mozinfo() {
1903   let mozinfoFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1904   mozinfoFile.initWithPath(_MOZINFO_JS_PATH);
1905   let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
1906     Ci.nsIFileInputStream
1907   );
1908   stream.init(mozinfoFile, -1, 0, 0);
1909   let bytes = _NetUtil.readInputStream(stream, stream.available());
1910   let decoded = JSON.parse(new TextDecoder().decode(bytes));
1911   stream.close();
1912   return decoded;
1915 Object.defineProperty(this, "mozinfo", {
1916   configurable: true,
1917   get() {
1918     let _mozinfo = _load_mozinfo();
1919     Object.defineProperty(this, "mozinfo", {
1920       configurable: false,
1921       value: _mozinfo,
1922     });
1923     return _mozinfo;
1924   },