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/. */
8 * This file contains common code that is loaded before each test file(s).
9 * See http://developer.mozilla.org/en/docs/Writing_xpcshell-based_unit_tests
10 * for more information.
15 var _tests_pending = 0;
16 var _passedChecks = 0, _falsePassedChecks = 0;
18 var _cleanupFunctions = [];
19 var _pendingTimers = [];
20 var _profileInitialized = false;
22 // Register the testing-common resource protocol early, to have access to its
24 _register_modules_protocol_handler();
26 let _Promise = Components.utils.import("resource://gre/modules/Promise.jsm", this).Promise;
28 // Support a common assertion library, Assert.jsm.
29 let AssertCls = Components.utils.import("resource://testing-common/Assert.jsm", null).Assert;
30 // Pass a custom report function for xpcshell-test style reporting.
31 let Assert = new AssertCls(function(err, message, stack) {
33 do_report_result(false, err.message, err.stack);
35 do_report_result(true, message, stack);
39 let _log = function (action, params) {
40 if (typeof _XPCSHELL_PROCESS != "undefined") {
41 params.process = _XPCSHELL_PROCESS;
43 params.action = action;
44 params._time = Date.now();
45 dump("\n" + JSON.stringify(params) + "\n");
49 let start = /^TEST-/.test(str) ? "\n" : "";
50 if (typeof _XPCSHELL_PROCESS == "undefined") {
53 dump(start + _XPCSHELL_PROCESS + ": " + str);
57 // Disable automatic network detection, so tests work correctly when
58 // not connected to a network.
59 let (ios = Components.classes["@mozilla.org/network/io-service;1"]
60 .getService(Components.interfaces.nsIIOService2)) {
61 ios.manageOfflineStatus = false;
65 // Determine if we're running on parent or child
66 let runningInParent = true;
68 runningInParent = Components.classes["@mozilla.org/xre/runtime;1"].
69 getService(Components.interfaces.nsIXULRuntime).processType
70 == Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
74 // Only if building of places is enabled.
75 if (runningInParent &&
76 "mozIAsyncHistory" in Components.interfaces) {
77 // Ensure places history is enabled for xpcshell-tests as some non-FF
79 let (prefs = Components.classes["@mozilla.org/preferences-service;1"]
80 .getService(Components.interfaces.nsIPrefBranch)) {
81 prefs.setBoolPref("places.history.enabled", true);
86 if (runningInParent) {
87 let prefs = Components.classes["@mozilla.org/preferences-service;1"]
88 .getService(Components.interfaces.nsIPrefBranch);
90 // disable necko IPC security checks for xpcshell, as they lack the
91 // docshells needed to pass them
92 prefs.setBoolPref("network.disable.ipc.security", true);
94 // Disable IPv6 lookups for 'localhost' on windows.
95 if ("@mozilla.org/windows-registry-key;1" in Components.classes) {
96 prefs.setCharPref("network.dns.ipv4OnlyDomains", "localhost");
102 // Configure crash reporting, if possible
103 // We rely on the Python harness to set MOZ_CRASHREPORTER,
104 // MOZ_CRASHREPORTER_NO_REPORT, and handle checking for minidumps.
105 // Note that if we're in a child process, we don't want to init the
106 // crashreporter component.
108 if (runningInParent &&
109 "@mozilla.org/toolkit/crash-reporter;1" in Components.classes) {
111 Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
112 .getService(Components.interfaces.nsICrashReporter)) {
113 crashReporter.UpdateCrashEventsDir();
114 crashReporter.minidumpPath = do_get_minidumpdir();
121 * Date.now() is not necessarily monotonically increasing (insert sob story
122 * about times not being the right tool to use for measuring intervals of time,
123 * robarnold can tell all), so be wary of error by erring by at least
126 const _timerFuzz = 15;
128 function _Timer(func, delay) {
129 delay = Number(delay);
131 do_throw("do_timeout() delay must be nonnegative");
133 if (typeof func !== "function")
134 do_throw("string callbacks no longer accepted; use a function!");
137 this._start = Date.now();
140 var timer = Components.classes["@mozilla.org/timer;1"]
141 .createInstance(Components.interfaces.nsITimer);
142 timer.initWithCallback(this, delay + _timerFuzz, timer.TYPE_ONE_SHOT);
144 // Keep timer alive until it fires
145 _pendingTimers.push(timer);
148 QueryInterface: function(iid) {
149 if (iid.equals(Components.interfaces.nsITimerCallback) ||
150 iid.equals(Components.interfaces.nsISupports))
153 throw Components.results.NS_ERROR_NO_INTERFACE;
156 notify: function(timer) {
157 _pendingTimers.splice(_pendingTimers.indexOf(timer), 1);
159 // The current nsITimer implementation can undershoot, but even if it
160 // couldn't, paranoia is probably a virtue here given the potential for
161 // random orange on tinderboxen.
162 var end = Date.now();
163 var elapsed = end - this._start;
164 if (elapsed >= this._delay) {
166 this._func.call(null);
168 do_throw("exception thrown from do_timeout callback: " + e);
173 // Timer undershot, retry with a little overshoot to try to avoid more
175 var newDelay = this._delay - elapsed;
176 do_timeout(newDelay, this._func);
180 function _do_main() {
185 {_message: "TEST-INFO | (xpcshell/head.js) | running event loop\n"});
187 var thr = Components.classes["@mozilla.org/thread-manager;1"]
188 .getService().currentThread;
191 thr.processNextEvent(true);
193 while (thr.hasPendingEvents())
194 thr.processNextEvent(true);
197 function _do_quit() {
199 {_message: "TEST-INFO | (xpcshell/head.js) | exiting test\n"});
200 _Promise.Debugging.flushUncaughtErrors();
204 function _format_exception_stack(stack) {
205 if (typeof stack == "object" && stack.caller) {
208 while (frame != null) {
209 strStack += frame + "\n";
210 frame = frame.caller;
214 // frame is of the form "fname@file:line"
215 let frame_regexp = new RegExp("(.*)@(.*):(\\d*)", "g");
216 return stack.split("\n").reduce(function(stack_msg, frame) {
218 let parts = frame_regexp.exec(frame);
220 let [ _, func, file, line ] = parts;
221 return stack_msg + "JS frame :: " + file + " :: " +
222 (func || "anonymous") + " :: line " + line + "\n";
224 else { /* Could be a -e (command line string) style location. */
225 return stack_msg + "JS frame :: " + frame + "\n";
233 * Overrides idleService with a mock. Idle is commonly used for maintenance
234 * tasks, thus if a test uses a service that requires the idle service, it will
235 * start handling them.
236 * This behaviour would cause random failures and slowdown tests execution,
237 * for example by running database vacuum or cleanups for each test.
239 * @note Idle service is overridden by default. If a test requires it, it will
240 * have to call do_get_idle() function at least once before use.
242 var _fakeIdleService = {
244 delete this.registrar;
245 return this.registrar =
246 Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
248 contractID: "@mozilla.org/widget/idleservice;1",
249 get CID() this.registrar.contractIDToCID(this.contractID),
251 activate: function FIS_activate()
253 if (!this.originalFactory) {
254 // Save original factory.
255 this.originalFactory =
256 Components.manager.getClassObject(Components.classes[this.contractID],
257 Components.interfaces.nsIFactory);
258 // Unregister original factory.
259 this.registrar.unregisterFactory(this.CID, this.originalFactory);
260 // Replace with the mock.
261 this.registrar.registerFactory(this.CID, "Fake Idle Service",
262 this.contractID, this.factory
267 deactivate: function FIS_deactivate()
269 if (this.originalFactory) {
270 // Unregister the mock.
271 this.registrar.unregisterFactory(this.CID, this.factory);
272 // Restore original factory.
273 this.registrar.registerFactory(this.CID, "Idle Service",
274 this.contractID, this.originalFactory);
275 delete this.originalFactory;
281 createInstance: function (aOuter, aIID)
284 throw Components.results.NS_ERROR_NO_AGGREGATION;
286 return _fakeIdleService.QueryInterface(aIID);
288 lockFactory: function (aLock) {
289 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
291 QueryInterface: function(aIID) {
292 if (aIID.equals(Components.interfaces.nsIFactory) ||
293 aIID.equals(Components.interfaces.nsISupports)) {
296 throw Components.results.NS_ERROR_NO_INTERFACE;
302 addIdleObserver: function () {},
303 removeIdleObserver: function () {},
305 QueryInterface: function(aIID) {
306 // Useful for testing purposes, see test_get_idle.js.
307 if (aIID.equals(Components.interfaces.nsIFactory)) {
310 if (aIID.equals(Components.interfaces.nsIIdleService) ||
311 aIID.equals(Components.interfaces.nsISupports)) {
314 throw Components.results.NS_ERROR_NO_INTERFACE;
319 * Restores the idle service factory if needed and returns the service's handle.
320 * @return A handle to the idle service.
322 function do_get_idle() {
323 _fakeIdleService.deactivate();
324 return Components.classes[_fakeIdleService.contractID]
325 .getService(Components.interfaces.nsIIdleService);
328 // Map resource://test/ to current working directory and
329 // resource://testing-common/ to the shared test modules directory.
330 function _register_protocol_handlers() {
331 let ios = Components.classes["@mozilla.org/network/io-service;1"]
332 .getService(Components.interfaces.nsIIOService);
333 let protocolHandler =
334 ios.getProtocolHandler("resource")
335 .QueryInterface(Components.interfaces.nsIResProtocolHandler);
337 let curDirURI = ios.newFileURI(do_get_cwd());
338 protocolHandler.setSubstitution("test", curDirURI);
340 _register_modules_protocol_handler();
343 function _register_modules_protocol_handler() {
344 if (!this._TESTING_MODULES_DIR) {
345 throw new Error("Please define a path where the testing modules can be " +
346 "found in a variable called '_TESTING_MODULES_DIR' before " +
347 "head.js is included.");
350 let ios = Components.classes["@mozilla.org/network/io-service;1"]
351 .getService(Components.interfaces.nsIIOService);
352 let protocolHandler =
353 ios.getProtocolHandler("resource")
354 .QueryInterface(Components.interfaces.nsIResProtocolHandler);
356 let modulesFile = Components.classes["@mozilla.org/file/local;1"].
357 createInstance(Components.interfaces.nsILocalFile);
358 modulesFile.initWithPath(_TESTING_MODULES_DIR);
360 if (!modulesFile.exists()) {
361 throw new Error("Specified modules directory does not exist: " +
362 _TESTING_MODULES_DIR);
365 if (!modulesFile.isDirectory()) {
366 throw new Error("Specified modules directory is not a directory: " +
367 _TESTING_MODULES_DIR);
370 let modulesURI = ios.newFileURI(modulesFile);
372 protocolHandler.setSubstitution("testing-common", modulesURI);
375 function _execute_test() {
376 _register_protocol_handlers();
378 // Override idle service by default.
379 // Call do_get_idle() to restore the factory and get the service.
380 _fakeIdleService.activate();
382 _Promise.Debugging.clearUncaughtErrorObservers();
383 _Promise.Debugging.addUncaughtErrorObserver(function observer({message, date, fileName, stack, lineNumber}) {
384 let text = " A promise chain failed to handle a rejection: " +
385 message + " - rejection date: " + date;
386 _log_message_with_stack("test_unexpected_fail",
387 text, stack, fileName);
390 // _HEAD_FILES is dynamically defined by <runxpcshelltests.py>.
391 _load_files(_HEAD_FILES);
392 // _TEST_FILE is dynamically defined by <runxpcshelltests.py>.
393 _load_files(_TEST_FILE);
395 // Tack Assert.jsm methods to the current scope.
396 this.Assert = Assert;
397 for (let func in Assert) {
398 this[func] = Assert[func].bind(Assert);
402 do_test_pending("MAIN run_test");
404 do_test_finished("MAIN run_test");
408 // do_check failures are already logged and set _quit to true and throw
409 // NS_ERROR_ABORT. If both of those are true it is likely this exception
410 // has already been logged so there is no need to log it again. It's
411 // possible that this will mask an NS_ERROR_ABORT that happens after a
412 // do_check failure though.
413 if (!_quit || e != Components.results.NS_ERROR_ABORT) {
416 msgObject.source_file = e.fileName;
418 msgObject.line_number = e.lineNumber;
421 msgObject.source_file = "xpcshell/head.js";
423 msgObject.diagnostic = _exception_message(e);
425 msgObject.diagnostic += " - See following stack:\n";
426 msgObject.stack = _format_exception_stack(e.stack);
428 _log("test_unexpected_fail", msgObject);
432 // _TAIL_FILES is dynamically defined by <runxpcshelltests.py>.
433 _load_files(_TAIL_FILES);
435 // Execute all of our cleanup functions.
436 let reportCleanupError = function(ex) {
438 if (ex && typeof ex == "object" && "stack" in ex) {
441 stack = Components.stack.caller;
443 if (stack instanceof Components.interfaces.nsIStackFrame) {
444 filename = stack.filename;
445 } else if (ex.fileName) {
446 filename = ex.fileName;
448 _log_message_with_stack("test_unexpected_fail",
449 ex, stack, filename);
453 while ((func = _cleanupFunctions.pop())) {
458 reportCleanupError(ex);
461 if (result && typeof result == "object"
462 && "then" in result && typeof result.then == "function") {
463 // This is a promise, wait until it is satisfied before proceeding
464 let complete = false;
465 let promise = result.then(null, reportCleanupError);
466 promise = promise.then(() => complete = true);
467 let thr = Components.classes["@mozilla.org/thread-manager;1"]
468 .getService().currentThread;
470 thr.processNextEvent(true);
475 // Restore idle service to avoid leaks.
476 _fakeIdleService.deactivate();
481 var truePassedChecks = _passedChecks - _falsePassedChecks;
482 if (truePassedChecks > 0) {
484 {_message: "TEST-PASS | (xpcshell/head.js) | " + truePassedChecks + " (+ " +
485 _falsePassedChecks + ") check(s) passed\n",
486 source_file: _TEST_FILE,
487 passed_checks: truePassedChecks});
489 {_message: "TEST-INFO | (xpcshell/head.js) | " + _todoChecks +
491 source_file: _TEST_FILE,
492 todo_checks: _todoChecks});
494 // ToDo: switch to TEST-UNEXPECTED-FAIL when all tests have been updated. (Bug 496443)
496 {_message: "TEST-INFO | (xpcshell/head.js) | No (+ " + _falsePassedChecks +
497 ") checks actually run\n",
498 source_file: _TEST_FILE});
505 * @param aFiles Array of files to load.
507 function _load_files(aFiles) {
508 function loadTailFile(element, index, array) {
511 } catch (e if e instanceof SyntaxError) {
512 _log("javascript_error",
513 {_message: "TEST-UNEXPECTED-FAIL | (xpcshell/head.js) | Source file " + element + " contains SyntaxError",
514 diagnostic: _exception_message(e),
515 source_file: element,
516 stack: _format_exception_stack(e.stack)});
518 _log("javascript_error",
519 {_message: "TEST-UNEXPECTED-FAIL | (xpcshell/head.js) | Source file " + element + " contains an error",
520 diagnostic: _exception_message(e),
521 source_file: element,
522 stack: e.stack ? _format_exception_stack(e.stack) : null});
526 aFiles.forEach(loadTailFile);
529 function _wrap_with_quotes_if_necessary(val) {
530 return typeof val == "string" ? '"' + val + '"' : val;
533 /************** Functions to be used from the tests **************/
536 * Prints a message to the output log.
538 function do_print(msg) {
539 var caller_stack = Components.stack.caller;
540 msg = _wrap_with_quotes_if_necessary(msg);
542 {source_file: caller_stack.filename,
548 * Calls the given function at least the specified number of milliseconds later.
549 * The callback will not undershoot the given time, but it might overshoot --
550 * don't expect precision!
552 * @param delay : uint
553 * the number of milliseconds to delay
554 * @param callback : function() : void
555 * the function to call
557 function do_timeout(delay, func) {
558 new _Timer(func, Number(delay));
561 function do_execute_soon(callback, aName) {
562 let funcName = (aName ? aName : callback.name);
563 do_test_pending(funcName);
564 var tm = Components.classes["@mozilla.org/thread-manager;1"]
565 .getService(Components.interfaces.nsIThreadManager);
567 tm.mainThread.dispatch({
572 // do_check failures are already logged and set _quit to true and throw
573 // NS_ERROR_ABORT. If both of those are true it is likely this exception
574 // has already been logged so there is no need to log it again. It's
575 // possible that this will mask an NS_ERROR_ABORT that happens after a
576 // do_check failure though.
577 if (!_quit || e != Components.results.NS_ERROR_ABORT) {
579 _log("javascript_error",
580 {source_file: "xpcshell/head.js",
581 diagnostic: _exception_message(e) + " - See following stack:",
582 stack: _format_exception_stack(e.stack)});
584 _log("javascript_error",
585 {source_file: "xpcshell/head.js",
586 diagnostic: _exception_message(e)});
592 do_test_finished(funcName);
595 }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
599 * Shows an error message and the current stack and aborts the test.
601 * @param error A message string or an Error object.
602 * @param stack null or nsIStackFrame object or a string containing
603 * \n separated stack lines (as in Error().stack).
605 function do_throw(error, stack) {
607 // If we didn't get passed a stack, maybe the error has one
608 // otherwise get it from our call context
609 stack = stack || error.stack || Components.stack.caller;
611 if (stack instanceof Components.interfaces.nsIStackFrame)
612 filename = stack.filename;
613 else if (error.fileName)
614 filename = error.fileName;
616 _log_message_with_stack("test_unexpected_fail",
617 error, stack, filename);
621 throw Components.results.NS_ERROR_ABORT;
624 function _format_stack(stack) {
626 if (stack instanceof Components.interfaces.nsIStackFrame) {
628 for (let frame = stack; frame; frame = frame.caller) {
629 frames.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
631 normalized = frames.join("\n");
633 normalized = "" + stack;
635 return _Task.Debugging.generateReadableStack(normalized, " ");
638 function do_throw_todo(text, stack) {
640 stack = Components.stack.caller;
643 _log_message_with_stack("test_unexpected_pass",
644 text, stack, stack.filename);
646 throw Components.results.NS_ERROR_ABORT;
649 // Make a nice display string from an object that behaves
651 function _exception_message(ex) {
654 message = ex.name + ": ";
657 message += ex.message;
660 message += (" at " + ex.fileName);
662 message += (":" + ex.lineNumber);
665 if (message !== "") {
668 // Force ex to be stringified
672 function _log_message_with_stack(action, ex, stack, filename, text) {
675 {diagnostic: (text ? text : "") +
676 _exception_message(ex) +
677 " - See following stack:",
678 source_file: filename,
679 stack: _format_stack(stack)});
682 {diagnostic: (text ? text : "") +
683 _exception_message(ex),
684 source_file: filename});
688 function do_report_unexpected_exception(ex, text) {
689 var caller_stack = Components.stack.caller;
690 text = text ? text + " - " : "";
693 _log_message_with_stack("test_unexpected_fail", ex, ex.stack || "",
694 caller_stack.filename, text + "Unexpected exception ");
696 throw Components.results.NS_ERROR_ABORT;
699 function do_note_exception(ex, text) {
700 var caller_stack = Components.stack.caller;
701 text = text ? text + " - " : "";
703 _log_message_with_stack("test_info", ex, ex.stack,
704 caller_stack.filename, text + "Swallowed exception ");
707 function _do_check_neq(left, right, stack, todo) {
708 Assert.notEqual(left, right);
711 function do_check_neq(left, right, stack) {
713 stack = Components.stack.caller;
715 _do_check_neq(left, right, stack, false);
718 function todo_check_neq(left, right, stack) {
720 stack = Components.stack.caller;
722 _do_check_neq(left, right, stack, true);
725 function do_report_result(passed, text, stack, todo) {
726 while (stack.filename.contains("head.js") && stack.caller) {
727 stack = stack.caller;
731 do_throw_todo(text, stack);
735 {source_file: stack.filename,
736 test_name: stack.name,
737 line_number: stack.lineNumber,
738 diagnostic: "[" + stack.name + " : " + stack.lineNumber + "] " +
744 _log("test_known_fail",
745 {source_file: stack.filename,
746 test_name: stack.name,
747 line_number: stack.lineNumber,
748 diagnostic: "[" + stack.name + " : " + stack.lineNumber + "] " +
751 do_throw(text, stack);
756 function _do_check_eq(left, right, stack, todo) {
758 stack = Components.stack.caller;
760 var text = _wrap_with_quotes_if_necessary(left) + " == " +
761 _wrap_with_quotes_if_necessary(right);
762 do_report_result(left == right, text, stack, todo);
765 function do_check_eq(left, right, stack) {
766 Assert.equal(left, right);
769 function todo_check_eq(left, right, stack) {
771 stack = Components.stack.caller;
773 _do_check_eq(left, right, stack, true);
776 function do_check_true(condition, stack) {
777 Assert.ok(condition);
780 function todo_check_true(condition, stack) {
782 stack = Components.stack.caller;
784 todo_check_eq(condition, true, stack);
787 function do_check_false(condition, stack) {
788 Assert.ok(!condition, stack);
791 function todo_check_false(condition, stack) {
793 stack = Components.stack.caller;
795 todo_check_eq(condition, false, stack);
798 function do_check_null(condition, stack) {
799 Assert.equal(condition, null);
802 function todo_check_null(condition, stack=Components.stack.caller) {
803 todo_check_eq(condition, null, stack);
805 function do_check_matches(pattern, value) {
806 Assert.deepEqual(pattern, value);
809 // Check that |func| throws an nsIException that has
810 // |Components.results[resultName]| as the value of its 'result' property.
811 function do_check_throws_nsIException(func, resultName,
812 stack=Components.stack.caller, todo=false)
814 let expected = Components.results[resultName];
815 if (typeof expected !== 'number') {
816 do_throw("do_check_throws_nsIException requires a Components.results" +
817 " property name, not " + uneval(resultName), stack);
820 let msg = ("do_check_throws_nsIException: func should throw" +
821 " an nsIException whose 'result' is Components.results." +
827 if (!(ex instanceof Components.interfaces.nsIException) ||
828 ex.result !== expected) {
829 do_report_result(false, msg + ", threw " + legible_exception(ex) +
830 " instead", stack, todo);
833 do_report_result(true, msg, stack, todo);
837 // Call this here, not in the 'try' clause, so do_report_result's own
838 // throw doesn't get caught by our 'catch' clause.
839 do_report_result(false, msg + ", but returned normally", stack, todo);
842 // Produce a human-readable form of |exception|. This looks up
843 // Components.results values, tries toString methods, and so on.
844 function legible_exception(exception)
846 switch (typeof exception) {
848 if (exception instanceof Components.interfaces.nsIException) {
849 return "nsIException instance: " + uneval(exception.toString());
851 return exception.toString();
854 for (let name in Components.results) {
855 if (exception === Components.results[name]) {
856 return "Components.results." + name;
862 return uneval(exception);
866 function do_check_instanceof(value, constructor,
867 stack=Components.stack.caller, todo=false) {
868 do_report_result(value instanceof constructor,
869 "value should be an instance of " + constructor.name,
873 function todo_check_instanceof(value, constructor,
874 stack=Components.stack.caller) {
875 do_check_instanceof(value, constructor, stack, true);
878 function do_test_pending(aName) {
882 {_message: "TEST-INFO | (xpcshell/head.js) | test" +
883 (aName ? " " + aName : "") +
884 " pending (" + _tests_pending + ")\n"});
887 function do_test_finished(aName) {
889 {_message: "TEST-INFO | (xpcshell/head.js) | test" +
890 (aName ? " " + aName : "") +
891 " finished (" + _tests_pending + ")\n"});
892 if (--_tests_pending == 0)
896 function do_get_file(path, allowNonexistent) {
898 let lf = Components.classes["@mozilla.org/file/directory_service;1"]
899 .getService(Components.interfaces.nsIProperties)
900 .get("CurWorkD", Components.interfaces.nsILocalFile);
902 let bits = path.split("/");
903 for (let i = 0; i < bits.length; i++) {
912 if (!allowNonexistent && !lf.exists()) {
913 // Not using do_throw(): caller will continue.
915 var stack = Components.stack.caller;
916 _log("test_unexpected_fail",
917 {source_file: stack.filename,
918 test_name: stack.name,
919 line_number: stack.lineNumber,
920 diagnostic: "[" + stack.name + " : " + stack.lineNumber + "] " +
921 lf.path + " does not exist\n"});
927 do_throw(ex.toString(), Components.stack.caller);
933 // do_get_cwd() isn't exactly self-explanatory, so provide a helper
934 function do_get_cwd() {
935 return do_get_file("");
938 function do_load_manifest(path) {
939 var lf = do_get_file(path);
940 const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
941 do_check_true(Components.manager instanceof nsIComponentRegistrar);
942 // Previous do_check_true() is not a test check.
943 ++_falsePassedChecks;
944 Components.manager.autoRegister(lf);
948 * Parse a DOM document.
950 * @param aPath File path to the document.
951 * @param aType Content type to use in DOMParser.
953 * @return nsIDOMDocument from the file.
955 function do_parse_document(aPath, aType) {
957 case "application/xhtml+xml":
958 case "application/xml":
963 do_throw("type: expected application/xhtml+xml, application/xml or text/xml," +
964 " got '" + aType + "'",
965 Components.stack.caller);
968 var lf = do_get_file(aPath);
969 const C_i = Components.interfaces;
970 const parserClass = "@mozilla.org/xmlextras/domparser;1";
971 const streamClass = "@mozilla.org/network/file-input-stream;1";
972 var stream = Components.classes[streamClass]
973 .createInstance(C_i.nsIFileInputStream);
974 stream.init(lf, -1, -1, C_i.nsIFileInputStream.CLOSE_ON_EOF);
975 var parser = Components.classes[parserClass]
976 .createInstance(C_i.nsIDOMParser);
977 var doc = parser.parseFromStream(stream, null, lf.fileSize, aType);
985 * Registers a function that will run when the test harness is done running all
989 * The function to be called when the test harness has finished running.
991 function do_register_cleanup(aFunction)
993 _cleanupFunctions.push(aFunction);
997 * Returns the directory for a temp dir, which is created by the
998 * test harness. Every test gets its own temp dir.
1000 * @return nsILocalFile of the temporary directory
1002 function do_get_tempdir() {
1003 let env = Components.classes["@mozilla.org/process/environment;1"]
1004 .getService(Components.interfaces.nsIEnvironment);
1005 // the python harness sets this in the environment for us
1006 let path = env.get("XPCSHELL_TEST_TEMP_DIR");
1007 let file = Components.classes["@mozilla.org/file/local;1"]
1008 .createInstance(Components.interfaces.nsILocalFile);
1009 file.initWithPath(path);
1014 * Returns the directory for crashreporter minidumps.
1016 * @return nsILocalFile of the minidump directory
1018 function do_get_minidumpdir() {
1019 let env = Components.classes["@mozilla.org/process/environment;1"]
1020 .getService(Components.interfaces.nsIEnvironment);
1021 // the python harness may set this in the environment for us
1022 let path = env.get("XPCSHELL_MINIDUMP_DIR");
1024 let file = Components.classes["@mozilla.org/file/local;1"]
1025 .createInstance(Components.interfaces.nsILocalFile);
1026 file.initWithPath(path);
1029 return do_get_tempdir();
1034 * Registers a directory with the profile service,
1035 * and return the directory as an nsILocalFile.
1037 * @return nsILocalFile of the profile directory.
1039 function do_get_profile() {
1040 if (!runningInParent) {
1042 {_message: "TEST-INFO | (xpcshell/head.js) | Ignoring profile creation from child process.\n"});
1047 if (!_profileInitialized) {
1048 // Since we have a profile, we will notify profile shutdown topics at
1049 // the end of the current test, to ensure correct cleanup on shutdown.
1050 do_register_cleanup(function() {
1051 let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
1052 getService(Components.interfaces.nsIObserverService);
1053 obsSvc.notifyObservers(null, "profile-change-net-teardown", null);
1054 obsSvc.notifyObservers(null, "profile-change-teardown", null);
1055 obsSvc.notifyObservers(null, "profile-before-change", null);
1059 let env = Components.classes["@mozilla.org/process/environment;1"]
1060 .getService(Components.interfaces.nsIEnvironment);
1061 // the python harness sets this in the environment for us
1062 let profd = env.get("XPCSHELL_TEST_PROFILE_DIR");
1063 let file = Components.classes["@mozilla.org/file/local;1"]
1064 .createInstance(Components.interfaces.nsILocalFile);
1065 file.initWithPath(profd);
1067 let dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
1068 .getService(Components.interfaces.nsIProperties);
1070 getFile: function(prop, persistent) {
1071 persistent.value = true;
1072 if (prop == "ProfD" || prop == "ProfLD" || prop == "ProfDS" ||
1073 prop == "ProfLDS" || prop == "TmpD") {
1074 return file.clone();
1078 QueryInterface: function(iid) {
1079 if (iid.equals(Components.interfaces.nsIDirectoryServiceProvider) ||
1080 iid.equals(Components.interfaces.nsISupports)) {
1083 throw Components.results.NS_ERROR_NO_INTERFACE;
1086 dirSvc.QueryInterface(Components.interfaces.nsIDirectoryService)
1087 .registerProvider(provider);
1089 let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
1090 getService(Components.interfaces.nsIObserverService);
1092 // We need to update the crash events directory when the profile changes.
1093 if (runningInParent &&
1094 "@mozilla.org/toolkit/crash-reporter;1" in Components.classes) {
1096 Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
1097 .getService(Components.interfaces.nsICrashReporter);
1098 crashReporter.UpdateCrashEventsDir();
1101 if (!_profileInitialized) {
1102 obsSvc.notifyObservers(null, "profile-do-change", "xpcshell-do-get-profile");
1103 _profileInitialized = true;
1106 // The methods of 'provider' will retain this scope so null out everything
1107 // to avoid spurious leak reports.
1113 return file.clone();
1117 * This function loads head.js (this file) in the child process, so that all
1118 * functions defined in this file (do_throw, etc) are available to subsequent
1119 * sendCommand calls. It also sets various constants used by these functions.
1121 * (Note that you may use sendCommand without calling this function first; you
1122 * simply won't have any of the functions in this file available.)
1124 function do_load_child_test_harness()
1126 // Make sure this isn't called from child process
1127 if (!runningInParent) {
1128 do_throw("run_test_in_child cannot be called from child!");
1131 // Allow to be called multiple times, but only run once
1132 if (typeof do_load_child_test_harness.alreadyRun != "undefined")
1134 do_load_child_test_harness.alreadyRun = 1;
1136 _XPCSHELL_PROCESS = "parent";
1139 "const _HEAD_JS_PATH=" + uneval(_HEAD_JS_PATH) + "; "
1140 + "const _HTTPD_JS_PATH=" + uneval(_HTTPD_JS_PATH) + "; "
1141 + "const _HEAD_FILES=" + uneval(_HEAD_FILES) + "; "
1142 + "const _TAIL_FILES=" + uneval(_TAIL_FILES) + "; "
1143 + "const _XPCSHELL_PROCESS='child';";
1145 if (this._TESTING_MODULES_DIR) {
1146 command += " const _TESTING_MODULES_DIR=" + uneval(_TESTING_MODULES_DIR) + ";";
1149 command += " load(_HEAD_JS_PATH);";
1150 sendCommand(command);
1154 * Runs an entire xpcshell unit test in a child process (rather than in chrome,
1155 * which is the default).
1157 * This function returns immediately, before the test has completed.
1160 * The name of the script to run. Path format same as load().
1161 * @param optionalCallback.
1162 * Optional function to be called (in parent) when test on child is
1163 * complete. If provided, the function must call do_test_finished();
1165 function run_test_in_child(testFile, optionalCallback)
1167 var callback = (typeof optionalCallback == 'undefined') ?
1168 do_test_finished : optionalCallback;
1170 do_load_child_test_harness();
1172 var testPath = do_get_file(testFile).path.replace(/\\/g, "/");
1173 do_test_pending("run in child");
1174 sendCommand("_log('child_test_start', {_message: 'CHILD-TEST-STARTED'}); "
1175 + "const _TEST_FILE=['" + testPath + "']; _execute_test(); "
1176 + "_log('child_test_end', {_message: 'CHILD-TEST-COMPLETED'});",
1181 * Execute a given function as soon as a particular cross-process message is received.
1182 * Must be paired with do_send_remote_message or equivalent ProcessMessageManager calls.
1184 function do_await_remote_message(name, callback)
1187 receiveMessage: function(message) {
1188 if (message.name == name) {
1189 mm.removeMessageListener(name, listener);
1197 if (runningInParent) {
1198 mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIMessageBroadcaster);
1200 mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
1203 mm.addMessageListener(name, listener);
1207 * Asynchronously send a message to all remote processes. Pairs with do_await_remote_message
1208 * or equivalent ProcessMessageManager listeners.
1210 function do_send_remote_message(name) {
1213 if (runningInParent) {
1214 mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIMessageBroadcaster);
1215 sender = 'broadcastAsyncMessage';
1217 mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
1218 sender = 'sendAsyncMessage';
1224 * Add a test function to the list of tests that are to be run asynchronously.
1226 * Each test function must call run_next_test() when it's done. Test files
1227 * should call run_next_test() in their run_test function to execute all
1230 * @return the test function that was passed in.
1233 function add_test(func) {
1234 _gTests.push([false, func]);
1239 * Add a test function which is a Task function.
1241 * Task functions are functions fed into Task.jsm's Task.spawn(). They are
1242 * generators that emit promises.
1244 * If an exception is thrown, a do_check_* comparison fails, or if a rejected
1245 * promise is yielded, the test function aborts immediately and the test is
1246 * reported as a failure.
1248 * Unlike add_test(), there is no need to call run_next_test(). The next test
1249 * will run automatically as soon the task function is exhausted. To trigger
1250 * premature (but successful) termination of the function, simply return or
1251 * throw a Task.Result instance.
1255 * add_task(function test() {
1256 * let result = yield Promise.resolve(true);
1258 * do_check_true(result);
1260 * let secondary = yield someFunctionThatReturnsAPromise(result);
1261 * do_check_eq(secondary, "expected value");
1264 * add_task(function test_early_return() {
1265 * let result = yield somethingThatReturnsAPromise();
1268 * // Test is ended immediately, with success.
1272 * do_check_eq(result, "foo");
1275 function add_task(func) {
1276 _gTests.push([true, func]);
1278 let _Task = Components.utils.import("resource://gre/modules/Task.jsm", {}).Task;
1279 _Task.Debugging.maintainStack = true;
1283 * Runs the next test function from the list of async tests.
1285 let _gRunningTest = null;
1286 let _gTestIndex = 0; // The index of the currently running test.
1287 let _gTaskRunning = false;
1288 function run_next_test()
1290 if (_gTaskRunning) {
1291 throw new Error("run_next_test() called from an add_task() test function. " +
1292 "run_next_test() should not be called from inside add_task() " +
1293 "under any circumstances!");
1296 function _run_next_test()
1298 if (_gTestIndex < _gTests.length) {
1299 // Flush uncaught errors as early and often as possible.
1300 _Promise.Debugging.flushUncaughtErrors();
1302 [_isTask, _gRunningTest] = _gTests[_gTestIndex++];
1303 print("TEST-INFO | " + _TEST_FILE + " | Starting " + _gRunningTest.name);
1304 do_test_pending(_gRunningTest.name);
1307 _gTaskRunning = true;
1308 _Task.spawn(_gRunningTest).then(
1309 () => { _gTaskRunning = false; run_next_test(); },
1310 (ex) => { _gTaskRunning = false; do_report_unexpected_exception(ex); }
1313 // Exceptions do not kill asynchronous tests, so they'll time out.
1323 // For sane stacks during failures, we execute this code soon, but not now.
1324 // We do this now, before we call do_test_finished(), to ensure the pending
1325 // counter (_tests_pending) never reaches 0 while we still have tests to run
1326 // (do_execute_soon bumps that counter).
1327 do_execute_soon(_run_next_test, "run_next_test " + _gTestIndex);
1329 if (_gRunningTest !== null) {
1330 // Close the previous test do_test_pending call.
1331 do_test_finished(_gRunningTest.name);
1336 if (runningInParent) {
1337 // Always use network provider for geolocation tests
1338 // so we bypass the OSX dialog raised by the corelocation provider
1339 let prefs = Components.classes["@mozilla.org/preferences-service;1"]
1340 .getService(Components.interfaces.nsIPrefBranch);
1342 prefs.setBoolPref("geo.provider.testing", true);
1346 // We need to avoid hitting the network with certain components.
1348 if (runningInParent) {
1349 let prefs = Components.classes["@mozilla.org/preferences-service;1"]
1350 .getService(Components.interfaces.nsIPrefBranch);
1352 prefs.setCharPref("media.gmp-manager.url.override", "http://%(server)s/dummy-gmp-manager.xml");