1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/xpcshell/head.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1491 @@ 1.4 +/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* 1.11 + * This file contains common code that is loaded before each test file(s). 1.12 + * See http://developer.mozilla.org/en/docs/Writing_xpcshell-based_unit_tests 1.13 + * for more information. 1.14 + */ 1.15 + 1.16 +var _quit = false; 1.17 +var _passed = true; 1.18 +var _tests_pending = 0; 1.19 +var _passedChecks = 0, _falsePassedChecks = 0; 1.20 +var _todoChecks = 0; 1.21 +var _cleanupFunctions = []; 1.22 +var _pendingTimers = []; 1.23 +var _profileInitialized = false; 1.24 + 1.25 +let _Promise = Components.utils.import("resource://gre/modules/Promise.jsm", this).Promise; 1.26 + 1.27 +let _log = function (action, params) { 1.28 + if (typeof _XPCSHELL_PROCESS != "undefined") { 1.29 + params.process = _XPCSHELL_PROCESS; 1.30 + } 1.31 + params.action = action; 1.32 + params._time = Date.now(); 1.33 + dump("\n" + JSON.stringify(params) + "\n"); 1.34 +} 1.35 + 1.36 +function _dump(str) { 1.37 + let start = /^TEST-/.test(str) ? "\n" : ""; 1.38 + if (typeof _XPCSHELL_PROCESS == "undefined") { 1.39 + dump(start + str); 1.40 + } else { 1.41 + dump(start + _XPCSHELL_PROCESS + ": " + str); 1.42 + } 1.43 +} 1.44 + 1.45 +// Disable automatic network detection, so tests work correctly when 1.46 +// not connected to a network. 1.47 +let (ios = Components.classes["@mozilla.org/network/io-service;1"] 1.48 + .getService(Components.interfaces.nsIIOService2)) { 1.49 + ios.manageOfflineStatus = false; 1.50 + ios.offline = false; 1.51 +} 1.52 + 1.53 +// Determine if we're running on parent or child 1.54 +let runningInParent = true; 1.55 +try { 1.56 + runningInParent = Components.classes["@mozilla.org/xre/runtime;1"]. 1.57 + getService(Components.interfaces.nsIXULRuntime).processType 1.58 + == Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT; 1.59 +} 1.60 +catch (e) { } 1.61 + 1.62 +// Only if building of places is enabled. 1.63 +if (runningInParent && 1.64 + "mozIAsyncHistory" in Components.interfaces) { 1.65 + // Ensure places history is enabled for xpcshell-tests as some non-FF 1.66 + // apps disable it. 1.67 + let (prefs = Components.classes["@mozilla.org/preferences-service;1"] 1.68 + .getService(Components.interfaces.nsIPrefBranch)) { 1.69 + prefs.setBoolPref("places.history.enabled", true); 1.70 + }; 1.71 +} 1.72 + 1.73 +try { 1.74 + if (runningInParent) { 1.75 + let prefs = Components.classes["@mozilla.org/preferences-service;1"] 1.76 + .getService(Components.interfaces.nsIPrefBranch); 1.77 + 1.78 + // disable necko IPC security checks for xpcshell, as they lack the 1.79 + // docshells needed to pass them 1.80 + prefs.setBoolPref("network.disable.ipc.security", true); 1.81 + 1.82 + // Disable IPv6 lookups for 'localhost' on windows. 1.83 + if ("@mozilla.org/windows-registry-key;1" in Components.classes) { 1.84 + prefs.setCharPref("network.dns.ipv4OnlyDomains", "localhost"); 1.85 + } 1.86 + } 1.87 +} 1.88 +catch (e) { } 1.89 + 1.90 +// Configure crash reporting, if possible 1.91 +// We rely on the Python harness to set MOZ_CRASHREPORTER, 1.92 +// MOZ_CRASHREPORTER_NO_REPORT, and handle checking for minidumps. 1.93 +// Note that if we're in a child process, we don't want to init the 1.94 +// crashreporter component. 1.95 +try { 1.96 + if (runningInParent && 1.97 + "@mozilla.org/toolkit/crash-reporter;1" in Components.classes) { 1.98 + let (crashReporter = 1.99 + Components.classes["@mozilla.org/toolkit/crash-reporter;1"] 1.100 + .getService(Components.interfaces.nsICrashReporter)) { 1.101 + crashReporter.UpdateCrashEventsDir(); 1.102 + crashReporter.minidumpPath = do_get_minidumpdir(); 1.103 + } 1.104 + } 1.105 +} 1.106 +catch (e) { } 1.107 + 1.108 +/** 1.109 + * Date.now() is not necessarily monotonically increasing (insert sob story 1.110 + * about times not being the right tool to use for measuring intervals of time, 1.111 + * robarnold can tell all), so be wary of error by erring by at least 1.112 + * _timerFuzz ms. 1.113 + */ 1.114 +const _timerFuzz = 15; 1.115 + 1.116 +function _Timer(func, delay) { 1.117 + delay = Number(delay); 1.118 + if (delay < 0) 1.119 + do_throw("do_timeout() delay must be nonnegative"); 1.120 + 1.121 + if (typeof func !== "function") 1.122 + do_throw("string callbacks no longer accepted; use a function!"); 1.123 + 1.124 + this._func = func; 1.125 + this._start = Date.now(); 1.126 + this._delay = delay; 1.127 + 1.128 + var timer = Components.classes["@mozilla.org/timer;1"] 1.129 + .createInstance(Components.interfaces.nsITimer); 1.130 + timer.initWithCallback(this, delay + _timerFuzz, timer.TYPE_ONE_SHOT); 1.131 + 1.132 + // Keep timer alive until it fires 1.133 + _pendingTimers.push(timer); 1.134 +} 1.135 +_Timer.prototype = { 1.136 + QueryInterface: function(iid) { 1.137 + if (iid.equals(Components.interfaces.nsITimerCallback) || 1.138 + iid.equals(Components.interfaces.nsISupports)) 1.139 + return this; 1.140 + 1.141 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.142 + }, 1.143 + 1.144 + notify: function(timer) { 1.145 + _pendingTimers.splice(_pendingTimers.indexOf(timer), 1); 1.146 + 1.147 + // The current nsITimer implementation can undershoot, but even if it 1.148 + // couldn't, paranoia is probably a virtue here given the potential for 1.149 + // random orange on tinderboxen. 1.150 + var end = Date.now(); 1.151 + var elapsed = end - this._start; 1.152 + if (elapsed >= this._delay) { 1.153 + try { 1.154 + this._func.call(null); 1.155 + } catch (e) { 1.156 + do_throw("exception thrown from do_timeout callback: " + e); 1.157 + } 1.158 + return; 1.159 + } 1.160 + 1.161 + // Timer undershot, retry with a little overshoot to try to avoid more 1.162 + // undershoots. 1.163 + var newDelay = this._delay - elapsed; 1.164 + do_timeout(newDelay, this._func); 1.165 + } 1.166 +}; 1.167 + 1.168 +function _do_main() { 1.169 + if (_quit) 1.170 + return; 1.171 + 1.172 + _log("test_info", 1.173 + {_message: "TEST-INFO | (xpcshell/head.js) | running event loop\n"}); 1.174 + 1.175 + var thr = Components.classes["@mozilla.org/thread-manager;1"] 1.176 + .getService().currentThread; 1.177 + 1.178 + while (!_quit) 1.179 + thr.processNextEvent(true); 1.180 + 1.181 + while (thr.hasPendingEvents()) 1.182 + thr.processNextEvent(true); 1.183 +} 1.184 + 1.185 +function _do_quit() { 1.186 + _log("test_info", 1.187 + {_message: "TEST-INFO | (xpcshell/head.js) | exiting test\n"}); 1.188 + _Promise.Debugging.flushUncaughtErrors(); 1.189 + _quit = true; 1.190 +} 1.191 + 1.192 +function _format_exception_stack(stack) { 1.193 + if (typeof stack == "object" && stack.caller) { 1.194 + let frame = stack; 1.195 + let strStack = ""; 1.196 + while (frame != null) { 1.197 + strStack += frame + "\n"; 1.198 + frame = frame.caller; 1.199 + } 1.200 + stack = strStack; 1.201 + } 1.202 + // frame is of the form "fname@file:line" 1.203 + let frame_regexp = new RegExp("(.*)@(.*):(\\d*)", "g"); 1.204 + return stack.split("\n").reduce(function(stack_msg, frame) { 1.205 + if (frame) { 1.206 + let parts = frame_regexp.exec(frame); 1.207 + if (parts) { 1.208 + let [ _, func, file, line ] = parts; 1.209 + return stack_msg + "JS frame :: " + file + " :: " + 1.210 + (func || "anonymous") + " :: line " + line + "\n"; 1.211 + } 1.212 + else { /* Could be a -e (command line string) style location. */ 1.213 + return stack_msg + "JS frame :: " + frame + "\n"; 1.214 + } 1.215 + } 1.216 + return stack_msg; 1.217 + }, ""); 1.218 +} 1.219 + 1.220 +/** 1.221 + * Overrides idleService with a mock. Idle is commonly used for maintenance 1.222 + * tasks, thus if a test uses a service that requires the idle service, it will 1.223 + * start handling them. 1.224 + * This behaviour would cause random failures and slowdown tests execution, 1.225 + * for example by running database vacuum or cleanups for each test. 1.226 + * 1.227 + * @note Idle service is overridden by default. If a test requires it, it will 1.228 + * have to call do_get_idle() function at least once before use. 1.229 + */ 1.230 +var _fakeIdleService = { 1.231 + get registrar() { 1.232 + delete this.registrar; 1.233 + return this.registrar = 1.234 + Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); 1.235 + }, 1.236 + contractID: "@mozilla.org/widget/idleservice;1", 1.237 + get CID() this.registrar.contractIDToCID(this.contractID), 1.238 + 1.239 + activate: function FIS_activate() 1.240 + { 1.241 + if (!this.originalFactory) { 1.242 + // Save original factory. 1.243 + this.originalFactory = 1.244 + Components.manager.getClassObject(Components.classes[this.contractID], 1.245 + Components.interfaces.nsIFactory); 1.246 + // Unregister original factory. 1.247 + this.registrar.unregisterFactory(this.CID, this.originalFactory); 1.248 + // Replace with the mock. 1.249 + this.registrar.registerFactory(this.CID, "Fake Idle Service", 1.250 + this.contractID, this.factory 1.251 + ); 1.252 + } 1.253 + }, 1.254 + 1.255 + deactivate: function FIS_deactivate() 1.256 + { 1.257 + if (this.originalFactory) { 1.258 + // Unregister the mock. 1.259 + this.registrar.unregisterFactory(this.CID, this.factory); 1.260 + // Restore original factory. 1.261 + this.registrar.registerFactory(this.CID, "Idle Service", 1.262 + this.contractID, this.originalFactory); 1.263 + delete this.originalFactory; 1.264 + } 1.265 + }, 1.266 + 1.267 + factory: { 1.268 + // nsIFactory 1.269 + createInstance: function (aOuter, aIID) 1.270 + { 1.271 + if (aOuter) { 1.272 + throw Components.results.NS_ERROR_NO_AGGREGATION; 1.273 + } 1.274 + return _fakeIdleService.QueryInterface(aIID); 1.275 + }, 1.276 + lockFactory: function (aLock) { 1.277 + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; 1.278 + }, 1.279 + QueryInterface: function(aIID) { 1.280 + if (aIID.equals(Components.interfaces.nsIFactory) || 1.281 + aIID.equals(Components.interfaces.nsISupports)) { 1.282 + return this; 1.283 + } 1.284 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.285 + } 1.286 + }, 1.287 + 1.288 + // nsIIdleService 1.289 + get idleTime() 0, 1.290 + addIdleObserver: function () {}, 1.291 + removeIdleObserver: function () {}, 1.292 + 1.293 + QueryInterface: function(aIID) { 1.294 + // Useful for testing purposes, see test_get_idle.js. 1.295 + if (aIID.equals(Components.interfaces.nsIFactory)) { 1.296 + return this.factory; 1.297 + } 1.298 + if (aIID.equals(Components.interfaces.nsIIdleService) || 1.299 + aIID.equals(Components.interfaces.nsISupports)) { 1.300 + return this; 1.301 + } 1.302 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.303 + } 1.304 +} 1.305 + 1.306 +/** 1.307 + * Restores the idle service factory if needed and returns the service's handle. 1.308 + * @return A handle to the idle service. 1.309 + */ 1.310 +function do_get_idle() { 1.311 + _fakeIdleService.deactivate(); 1.312 + return Components.classes[_fakeIdleService.contractID] 1.313 + .getService(Components.interfaces.nsIIdleService); 1.314 +} 1.315 + 1.316 +// Map resource://test/ to current working directory and 1.317 +// resource://testing-common/ to the shared test modules directory. 1.318 +function _register_protocol_handlers() { 1.319 + let (ios = Components.classes["@mozilla.org/network/io-service;1"] 1.320 + .getService(Components.interfaces.nsIIOService)) { 1.321 + let protocolHandler = 1.322 + ios.getProtocolHandler("resource") 1.323 + .QueryInterface(Components.interfaces.nsIResProtocolHandler); 1.324 + let curDirURI = ios.newFileURI(do_get_cwd()); 1.325 + protocolHandler.setSubstitution("test", curDirURI); 1.326 + 1.327 + if (this._TESTING_MODULES_DIR) { 1.328 + let modulesFile = Components.classes["@mozilla.org/file/local;1"]. 1.329 + createInstance(Components.interfaces.nsILocalFile); 1.330 + modulesFile.initWithPath(_TESTING_MODULES_DIR); 1.331 + 1.332 + if (!modulesFile.exists()) { 1.333 + throw new Error("Specified modules directory does not exist: " + 1.334 + _TESTING_MODULES_DIR); 1.335 + } 1.336 + 1.337 + if (!modulesFile.isDirectory()) { 1.338 + throw new Error("Specified modules directory is not a directory: " + 1.339 + _TESTING_MODULES_DIR); 1.340 + } 1.341 + 1.342 + let modulesURI = ios.newFileURI(modulesFile); 1.343 + 1.344 + protocolHandler.setSubstitution("testing-common", modulesURI); 1.345 + } 1.346 + } 1.347 +} 1.348 + 1.349 +function _execute_test() { 1.350 + _register_protocol_handlers(); 1.351 + 1.352 + // Override idle service by default. 1.353 + // Call do_get_idle() to restore the factory and get the service. 1.354 + _fakeIdleService.activate(); 1.355 + 1.356 + _Promise.Debugging.clearUncaughtErrorObservers(); 1.357 + _Promise.Debugging.addUncaughtErrorObserver(function observer({message, date, fileName, stack, lineNumber}) { 1.358 + let text = "Once bug 976205 has landed, THIS ERROR WILL CAUSE A TEST FAILURE.\n" + 1.359 + " A promise chain failed to handle a rejection: " + 1.360 + message + " - rejection date: " + date; 1.361 + _log_message_with_stack("test_known_fail", 1.362 + text, stack, fileName); 1.363 + }); 1.364 + 1.365 + // _HEAD_FILES is dynamically defined by <runxpcshelltests.py>. 1.366 + _load_files(_HEAD_FILES); 1.367 + // _TEST_FILE is dynamically defined by <runxpcshelltests.py>. 1.368 + _load_files(_TEST_FILE); 1.369 + 1.370 + // Support a common assertion library, Assert.jsm. 1.371 + let Assert = Components.utils.import("resource://testing-common/Assert.jsm", null).Assert; 1.372 + // Pass a custom report function for xpcshell-test style reporting. 1.373 + let assertImpl = new Assert(function(err, message, stack) { 1.374 + if (err) { 1.375 + do_report_result(false, err.message, err.stack); 1.376 + } else { 1.377 + do_report_result(true, message, stack); 1.378 + } 1.379 + }); 1.380 + // Allow Assert.jsm methods to be tacked to the current scope. 1.381 + this.export_assertions = function() { 1.382 + for (let func in assertImpl) { 1.383 + this[func] = assertImpl[func].bind(assertImpl); 1.384 + } 1.385 + }; 1.386 + this.Assert = assertImpl; 1.387 + 1.388 + try { 1.389 + do_test_pending("MAIN run_test"); 1.390 + run_test(); 1.391 + do_test_finished("MAIN run_test"); 1.392 + _do_main(); 1.393 + } catch (e) { 1.394 + _passed = false; 1.395 + // do_check failures are already logged and set _quit to true and throw 1.396 + // NS_ERROR_ABORT. If both of those are true it is likely this exception 1.397 + // has already been logged so there is no need to log it again. It's 1.398 + // possible that this will mask an NS_ERROR_ABORT that happens after a 1.399 + // do_check failure though. 1.400 + if (!_quit || e != Components.results.NS_ERROR_ABORT) { 1.401 + let msgObject = {}; 1.402 + if (e.fileName) { 1.403 + msgObject.source_file = e.fileName; 1.404 + if (e.lineNumber) { 1.405 + msgObject.line_number = e.lineNumber; 1.406 + } 1.407 + } else { 1.408 + msgObject.source_file = "xpcshell/head.js"; 1.409 + } 1.410 + msgObject.diagnostic = _exception_message(e); 1.411 + if (e.stack) { 1.412 + msgObject.diagnostic += " - See following stack:\n"; 1.413 + msgObject.stack = _format_exception_stack(e.stack); 1.414 + } 1.415 + _log("test_unexpected_fail", msgObject); 1.416 + } 1.417 + } 1.418 + 1.419 + // _TAIL_FILES is dynamically defined by <runxpcshelltests.py>. 1.420 + _load_files(_TAIL_FILES); 1.421 + 1.422 + // Execute all of our cleanup functions. 1.423 + let reportCleanupError = function(ex) { 1.424 + let stack, filename; 1.425 + if (ex && typeof ex == "object" && "stack" in ex) { 1.426 + stack = ex.stack; 1.427 + } else { 1.428 + stack = Components.stack.caller; 1.429 + } 1.430 + if (stack instanceof Components.interfaces.nsIStackFrame) { 1.431 + filename = stack.filename; 1.432 + } else if (ex.fileName) { 1.433 + filename = ex.fileName; 1.434 + } 1.435 + _log_message_with_stack("test_unexpected_fail", 1.436 + ex, stack, filename); 1.437 + }; 1.438 + 1.439 + let func; 1.440 + while ((func = _cleanupFunctions.pop())) { 1.441 + let result; 1.442 + try { 1.443 + result = func(); 1.444 + } catch (ex) { 1.445 + reportCleanupError(ex); 1.446 + continue; 1.447 + } 1.448 + if (result && typeof result == "object" 1.449 + && "then" in result && typeof result.then == "function") { 1.450 + // This is a promise, wait until it is satisfied before proceeding 1.451 + let complete = false; 1.452 + let promise = result.then(null, reportCleanupError); 1.453 + promise = promise.then(() => complete = true); 1.454 + let thr = Components.classes["@mozilla.org/thread-manager;1"] 1.455 + .getService().currentThread; 1.456 + while (!complete) { 1.457 + thr.processNextEvent(true); 1.458 + } 1.459 + } 1.460 + } 1.461 + 1.462 + // Restore idle service to avoid leaks. 1.463 + _fakeIdleService.deactivate(); 1.464 + 1.465 + if (!_passed) 1.466 + return; 1.467 + 1.468 + var truePassedChecks = _passedChecks - _falsePassedChecks; 1.469 + if (truePassedChecks > 0) { 1.470 + _log("test_pass", 1.471 + {_message: "TEST-PASS | (xpcshell/head.js) | " + truePassedChecks + " (+ " + 1.472 + _falsePassedChecks + ") check(s) passed\n", 1.473 + source_file: _TEST_FILE, 1.474 + passed_checks: truePassedChecks}); 1.475 + _log("test_info", 1.476 + {_message: "TEST-INFO | (xpcshell/head.js) | " + _todoChecks + 1.477 + " check(s) todo\n", 1.478 + source_file: _TEST_FILE, 1.479 + todo_checks: _todoChecks}); 1.480 + } else { 1.481 + // ToDo: switch to TEST-UNEXPECTED-FAIL when all tests have been updated. (Bug 496443) 1.482 + _log("test_info", 1.483 + {_message: "TEST-INFO | (xpcshell/head.js) | No (+ " + _falsePassedChecks + 1.484 + ") checks actually run\n", 1.485 + source_file: _TEST_FILE}); 1.486 + } 1.487 +} 1.488 + 1.489 +/** 1.490 + * Loads files. 1.491 + * 1.492 + * @param aFiles Array of files to load. 1.493 + */ 1.494 +function _load_files(aFiles) { 1.495 + function loadTailFile(element, index, array) { 1.496 + try { 1.497 + load(element); 1.498 + } catch (e if e instanceof SyntaxError) { 1.499 + _log("javascript_error", 1.500 + {_message: "TEST-UNEXPECTED-FAIL | (xpcshell/head.js) | Source file " + element + " contains SyntaxError", 1.501 + diagnostic: _exception_message(e), 1.502 + source_file: element, 1.503 + stack: _format_exception_stack(e.stack)}); 1.504 + } catch (e) { 1.505 + _log("javascript_error", 1.506 + {_message: "TEST-UNEXPECTED-FAIL | (xpcshell/head.js) | Source file " + element + " contains an error", 1.507 + diagnostic: _exception_message(e), 1.508 + source_file: element, 1.509 + stack: _format_exception_stack(e.stack)}); 1.510 + } 1.511 + } 1.512 + 1.513 + aFiles.forEach(loadTailFile); 1.514 +} 1.515 + 1.516 +function _wrap_with_quotes_if_necessary(val) { 1.517 + return typeof val == "string" ? '"' + val + '"' : val; 1.518 +} 1.519 + 1.520 +/************** Functions to be used from the tests **************/ 1.521 + 1.522 +/** 1.523 + * Prints a message to the output log. 1.524 + */ 1.525 +function do_print(msg) { 1.526 + var caller_stack = Components.stack.caller; 1.527 + msg = _wrap_with_quotes_if_necessary(msg); 1.528 + _log("test_info", 1.529 + {source_file: caller_stack.filename, 1.530 + diagnostic: msg}); 1.531 + 1.532 +} 1.533 + 1.534 +/** 1.535 + * Calls the given function at least the specified number of milliseconds later. 1.536 + * The callback will not undershoot the given time, but it might overshoot -- 1.537 + * don't expect precision! 1.538 + * 1.539 + * @param delay : uint 1.540 + * the number of milliseconds to delay 1.541 + * @param callback : function() : void 1.542 + * the function to call 1.543 + */ 1.544 +function do_timeout(delay, func) { 1.545 + new _Timer(func, Number(delay)); 1.546 +} 1.547 + 1.548 +function do_execute_soon(callback, aName) { 1.549 + let funcName = (aName ? aName : callback.name); 1.550 + do_test_pending(funcName); 1.551 + var tm = Components.classes["@mozilla.org/thread-manager;1"] 1.552 + .getService(Components.interfaces.nsIThreadManager); 1.553 + 1.554 + tm.mainThread.dispatch({ 1.555 + run: function() { 1.556 + try { 1.557 + callback(); 1.558 + } catch (e) { 1.559 + // do_check failures are already logged and set _quit to true and throw 1.560 + // NS_ERROR_ABORT. If both of those are true it is likely this exception 1.561 + // has already been logged so there is no need to log it again. It's 1.562 + // possible that this will mask an NS_ERROR_ABORT that happens after a 1.563 + // do_check failure though. 1.564 + if (!_quit || e != Components.results.NS_ERROR_ABORT) { 1.565 + if (e.stack) { 1.566 + _log("javascript_error", 1.567 + {source_file: "xpcshell/head.js", 1.568 + diagnostic: _exception_message(e) + " - See following stack:", 1.569 + stack: _format_exception_stack(e.stack)}); 1.570 + } else { 1.571 + _log("javascript_error", 1.572 + {source_file: "xpcshell/head.js", 1.573 + diagnostic: _exception_message(e)}); 1.574 + } 1.575 + _do_quit(); 1.576 + } 1.577 + } 1.578 + finally { 1.579 + do_test_finished(funcName); 1.580 + } 1.581 + } 1.582 + }, Components.interfaces.nsIThread.DISPATCH_NORMAL); 1.583 +} 1.584 + 1.585 +/** 1.586 + * Shows an error message and the current stack and aborts the test. 1.587 + * 1.588 + * @param error A message string or an Error object. 1.589 + * @param stack null or nsIStackFrame object or a string containing 1.590 + * \n separated stack lines (as in Error().stack). 1.591 + */ 1.592 +function do_throw(error, stack) { 1.593 + let filename = ""; 1.594 + // If we didn't get passed a stack, maybe the error has one 1.595 + // otherwise get it from our call context 1.596 + stack = stack || error.stack || Components.stack.caller; 1.597 + 1.598 + if (stack instanceof Components.interfaces.nsIStackFrame) 1.599 + filename = stack.filename; 1.600 + else if (error.fileName) 1.601 + filename = error.fileName; 1.602 + 1.603 + _log_message_with_stack("test_unexpected_fail", 1.604 + error, stack, filename); 1.605 + 1.606 + _passed = false; 1.607 + _do_quit(); 1.608 + throw Components.results.NS_ERROR_ABORT; 1.609 +} 1.610 + 1.611 +function _format_stack(stack) { 1.612 + if (stack instanceof Components.interfaces.nsIStackFrame) { 1.613 + let stack_msg = ""; 1.614 + let frame = stack; 1.615 + while (frame != null) { 1.616 + stack_msg += frame + "\n"; 1.617 + frame = frame.caller; 1.618 + } 1.619 + return stack_msg; 1.620 + } 1.621 + return "" + stack; 1.622 +} 1.623 + 1.624 +function do_throw_todo(text, stack) { 1.625 + if (!stack) 1.626 + stack = Components.stack.caller; 1.627 + 1.628 + _passed = false; 1.629 + _log_message_with_stack("test_unexpected_pass", 1.630 + text, stack, stack.filename); 1.631 + _do_quit(); 1.632 + throw Components.results.NS_ERROR_ABORT; 1.633 +} 1.634 + 1.635 +// Make a nice display string from an object that behaves 1.636 +// like Error 1.637 +function _exception_message(ex) { 1.638 + let message = ""; 1.639 + if (ex.name) { 1.640 + message = ex.name + ": "; 1.641 + } 1.642 + if (ex.message) { 1.643 + message += ex.message; 1.644 + } 1.645 + if (ex.fileName) { 1.646 + message += (" at " + ex.fileName); 1.647 + if (ex.lineNumber) { 1.648 + message += (":" + ex.lineNumber); 1.649 + } 1.650 + } 1.651 + if (message !== "") { 1.652 + return message; 1.653 + } 1.654 + // Force ex to be stringified 1.655 + return "" + ex; 1.656 +} 1.657 + 1.658 +function _log_message_with_stack(action, ex, stack, filename, text) { 1.659 + if (stack) { 1.660 + _log(action, 1.661 + {diagnostic: (text ? text : "") + 1.662 + _exception_message(ex) + 1.663 + " - See following stack:", 1.664 + source_file: filename, 1.665 + stack: _format_stack(stack)}); 1.666 + } else { 1.667 + _log(action, 1.668 + {diagnostic: (text ? text : "") + 1.669 + _exception_message(ex), 1.670 + source_file: filename}); 1.671 + } 1.672 +} 1.673 + 1.674 +function do_report_unexpected_exception(ex, text) { 1.675 + var caller_stack = Components.stack.caller; 1.676 + text = text ? text + " - " : ""; 1.677 + 1.678 + _passed = false; 1.679 + _log_message_with_stack("test_unexpected_fail", ex, ex.stack, 1.680 + caller_stack.filename, text + "Unexpected exception "); 1.681 + _do_quit(); 1.682 + throw Components.results.NS_ERROR_ABORT; 1.683 +} 1.684 + 1.685 +function do_note_exception(ex, text) { 1.686 + var caller_stack = Components.stack.caller; 1.687 + text = text ? text + " - " : ""; 1.688 + 1.689 + _log_message_with_stack("test_info", ex, ex.stack, 1.690 + caller_stack.filename, text + "Swallowed exception "); 1.691 +} 1.692 + 1.693 +function _do_check_neq(left, right, stack, todo) { 1.694 + if (!stack) 1.695 + stack = Components.stack.caller; 1.696 + 1.697 + var text = _wrap_with_quotes_if_necessary(left) + " != " + 1.698 + _wrap_with_quotes_if_necessary(right); 1.699 + do_report_result(left != right, text, stack, todo); 1.700 +} 1.701 + 1.702 +function do_check_neq(left, right, stack) { 1.703 + if (!stack) 1.704 + stack = Components.stack.caller; 1.705 + 1.706 + _do_check_neq(left, right, stack, false); 1.707 +} 1.708 + 1.709 +function todo_check_neq(left, right, stack) { 1.710 + if (!stack) 1.711 + stack = Components.stack.caller; 1.712 + 1.713 + _do_check_neq(left, right, stack, true); 1.714 +} 1.715 + 1.716 +function do_report_result(passed, text, stack, todo) { 1.717 + if (passed) { 1.718 + if (todo) { 1.719 + do_throw_todo(text, stack); 1.720 + } else { 1.721 + ++_passedChecks; 1.722 + _log("test_pass", 1.723 + {source_file: stack.filename, 1.724 + test_name: stack.name, 1.725 + line_number: stack.lineNumber, 1.726 + diagnostic: "[" + stack.name + " : " + stack.lineNumber + "] " + 1.727 + text + "\n"}); 1.728 + } 1.729 + } else { 1.730 + if (todo) { 1.731 + ++_todoChecks; 1.732 + _log("test_known_fail", 1.733 + {source_file: stack.filename, 1.734 + test_name: stack.name, 1.735 + line_number: stack.lineNumber, 1.736 + diagnostic: "[" + stack.name + " : " + stack.lineNumber + "] " + 1.737 + text + "\n"}); 1.738 + } else { 1.739 + do_throw(text, stack); 1.740 + } 1.741 + } 1.742 +} 1.743 + 1.744 +function _do_check_eq(left, right, stack, todo) { 1.745 + if (!stack) 1.746 + stack = Components.stack.caller; 1.747 + 1.748 + var text = _wrap_with_quotes_if_necessary(left) + " == " + 1.749 + _wrap_with_quotes_if_necessary(right); 1.750 + do_report_result(left == right, text, stack, todo); 1.751 +} 1.752 + 1.753 +function do_check_eq(left, right, stack) { 1.754 + if (!stack) 1.755 + stack = Components.stack.caller; 1.756 + 1.757 + _do_check_eq(left, right, stack, false); 1.758 +} 1.759 + 1.760 +function todo_check_eq(left, right, stack) { 1.761 + if (!stack) 1.762 + stack = Components.stack.caller; 1.763 + 1.764 + _do_check_eq(left, right, stack, true); 1.765 +} 1.766 + 1.767 +function do_check_true(condition, stack) { 1.768 + if (!stack) 1.769 + stack = Components.stack.caller; 1.770 + 1.771 + do_check_eq(condition, true, stack); 1.772 +} 1.773 + 1.774 +function todo_check_true(condition, stack) { 1.775 + if (!stack) 1.776 + stack = Components.stack.caller; 1.777 + 1.778 + todo_check_eq(condition, true, stack); 1.779 +} 1.780 + 1.781 +function do_check_false(condition, stack) { 1.782 + if (!stack) 1.783 + stack = Components.stack.caller; 1.784 + 1.785 + do_check_eq(condition, false, stack); 1.786 +} 1.787 + 1.788 +function todo_check_false(condition, stack) { 1.789 + if (!stack) 1.790 + stack = Components.stack.caller; 1.791 + 1.792 + todo_check_eq(condition, false, stack); 1.793 +} 1.794 + 1.795 +function do_check_null(condition, stack=Components.stack.caller) { 1.796 + do_check_eq(condition, null, stack); 1.797 +} 1.798 + 1.799 +function todo_check_null(condition, stack=Components.stack.caller) { 1.800 + todo_check_eq(condition, null, stack); 1.801 +} 1.802 + 1.803 +/** 1.804 + * Check that |value| matches |pattern|. 1.805 + * 1.806 + * A |value| matches a pattern |pattern| if any one of the following is true: 1.807 + * 1.808 + * - |value| and |pattern| are both objects; |pattern|'s enumerable 1.809 + * properties' values are valid patterns; and for each enumerable 1.810 + * property |p| of |pattern|, plus 'length' if present at all, |value| 1.811 + * has a property |p| whose value matches |pattern.p|. Note that if |j| 1.812 + * has other properties not present in |p|, |j| may still match |p|. 1.813 + * 1.814 + * - |value| and |pattern| are equal string, numeric, or boolean literals 1.815 + * 1.816 + * - |pattern| is |undefined| (this is a wildcard pattern) 1.817 + * 1.818 + * - typeof |pattern| == "function", and |pattern(value)| is true. 1.819 + * 1.820 + * For example: 1.821 + * 1.822 + * do_check_matches({x:1}, {x:1}) // pass 1.823 + * do_check_matches({x:1}, {}) // fail: all pattern props required 1.824 + * do_check_matches({x:1}, {x:2}) // fail: values must match 1.825 + * do_check_matches({x:1}, {x:1, y:2}) // pass: extra props tolerated 1.826 + * 1.827 + * // Property order is irrelevant. 1.828 + * do_check_matches({x:"foo", y:"bar"}, {y:"bar", x:"foo"}) // pass 1.829 + * 1.830 + * do_check_matches({x:undefined}, {x:1}) // pass: 'undefined' is wildcard 1.831 + * do_check_matches({x:undefined}, {x:2}) 1.832 + * do_check_matches({x:undefined}, {y:2}) // fail: 'x' must still be there 1.833 + * 1.834 + * // Patterns nest. 1.835 + * do_check_matches({a:1, b:{c:2,d:undefined}}, {a:1, b:{c:2,d:3}}) 1.836 + * 1.837 + * // 'length' property counts, even if non-enumerable. 1.838 + * do_check_matches([3,4,5], [3,4,5]) // pass 1.839 + * do_check_matches([3,4,5], [3,5,5]) // fail; value doesn't match 1.840 + * do_check_matches([3,4,5], [3,4,5,6]) // fail; length doesn't match 1.841 + * 1.842 + * // functions in patterns get applied. 1.843 + * do_check_matches({foo:function (v) v.length == 2}, {foo:"hi"}) // pass 1.844 + * do_check_matches({foo:function (v) v.length == 2}, {bar:"hi"}) // fail 1.845 + * do_check_matches({foo:function (v) v.length == 2}, {foo:"hello"}) // fail 1.846 + * 1.847 + * // We don't check constructors, prototypes, or classes. However, if 1.848 + * // pattern has a 'length' property, we require values to match that as 1.849 + * // well, even if 'length' is non-enumerable in the pattern. So arrays 1.850 + * // are useful as patterns. 1.851 + * do_check_matches({0:0, 1:1, length:2}, [0,1]) // pass 1.852 + * do_check_matches({0:1}, [1,2]) // pass 1.853 + * do_check_matches([0], {0:0, length:1}) // pass 1.854 + * 1.855 + * Notes: 1.856 + * 1.857 + * The 'length' hack gives us reasonably intuitive handling of arrays. 1.858 + * 1.859 + * This is not a tight pattern-matcher; it's only good for checking data 1.860 + * from well-behaved sources. For example: 1.861 + * - By default, we don't mind values having extra properties. 1.862 + * - We don't check for proxies or getters. 1.863 + * - We don't check the prototype chain. 1.864 + * However, if you know the values are, say, JSON, which is pretty 1.865 + * well-behaved, and if you want to tolerate additional properties 1.866 + * appearing on the JSON for backward-compatibility, then do_check_matches 1.867 + * is ideal. If you do want to be more careful, you can use function 1.868 + * patterns to implement more stringent checks. 1.869 + */ 1.870 +function do_check_matches(pattern, value, stack=Components.stack.caller, todo=false) { 1.871 + var matcher = pattern_matcher(pattern); 1.872 + var text = "VALUE: " + uneval(value) + "\nPATTERN: " + uneval(pattern) + "\n"; 1.873 + var diagnosis = [] 1.874 + if (matcher(value, diagnosis)) { 1.875 + do_report_result(true, "value matches pattern:\n" + text, stack, todo); 1.876 + } else { 1.877 + text = ("value doesn't match pattern:\n" + 1.878 + text + 1.879 + "DIAGNOSIS: " + 1.880 + format_pattern_match_failure(diagnosis[0]) + "\n"); 1.881 + do_report_result(false, text, stack, todo); 1.882 + } 1.883 +} 1.884 + 1.885 +function todo_check_matches(pattern, value, stack=Components.stack.caller) { 1.886 + do_check_matches(pattern, value, stack, true); 1.887 +} 1.888 + 1.889 +// Return a pattern-matching function of one argument, |value|, that 1.890 +// returns true if |value| matches |pattern|. 1.891 +// 1.892 +// If the pattern doesn't match, and the pattern-matching function was 1.893 +// passed its optional |diagnosis| argument, the pattern-matching function 1.894 +// sets |diagnosis|'s '0' property to a JSON-ish description of the portion 1.895 +// of the pattern that didn't match, which can be formatted legibly by 1.896 +// format_pattern_match_failure. 1.897 +function pattern_matcher(pattern) { 1.898 + function explain(diagnosis, reason) { 1.899 + if (diagnosis) { 1.900 + diagnosis[0] = reason; 1.901 + } 1.902 + return false; 1.903 + } 1.904 + if (typeof pattern == "function") { 1.905 + return pattern; 1.906 + } else if (typeof pattern == "object" && pattern) { 1.907 + var matchers = [[p, pattern_matcher(pattern[p])] for (p in pattern)]; 1.908 + // Kludge: include 'length', if not enumerable. (If it is enumerable, 1.909 + // we picked it up in the array comprehension, above. 1.910 + ld = Object.getOwnPropertyDescriptor(pattern, 'length'); 1.911 + if (ld && !ld.enumerable) { 1.912 + matchers.push(['length', pattern_matcher(pattern.length)]) 1.913 + } 1.914 + return function (value, diagnosis) { 1.915 + if (!(value && typeof value == "object")) { 1.916 + return explain(diagnosis, "value not object"); 1.917 + } 1.918 + for (let [p, m] of matchers) { 1.919 + var element_diagnosis = []; 1.920 + if (!(p in value && m(value[p], element_diagnosis))) { 1.921 + return explain(diagnosis, { property:p, 1.922 + diagnosis:element_diagnosis[0] }); 1.923 + } 1.924 + } 1.925 + return true; 1.926 + }; 1.927 + } else if (pattern === undefined) { 1.928 + return function(value) { return true; }; 1.929 + } else { 1.930 + return function (value, diagnosis) { 1.931 + if (value !== pattern) { 1.932 + return explain(diagnosis, "pattern " + uneval(pattern) + " not === to value " + uneval(value)); 1.933 + } 1.934 + return true; 1.935 + }; 1.936 + } 1.937 +} 1.938 + 1.939 +// Format an explanation for a pattern match failure, as stored in the 1.940 +// second argument to a matching function. 1.941 +function format_pattern_match_failure(diagnosis, indent="") { 1.942 + var a; 1.943 + if (!diagnosis) { 1.944 + a = "Matcher did not explain reason for mismatch."; 1.945 + } else if (typeof diagnosis == "string") { 1.946 + a = diagnosis; 1.947 + } else if (diagnosis.property) { 1.948 + a = "Property " + uneval(diagnosis.property) + " of object didn't match:\n"; 1.949 + a += format_pattern_match_failure(diagnosis.diagnosis, indent + " "); 1.950 + } 1.951 + return indent + a; 1.952 +} 1.953 + 1.954 +// Check that |func| throws an nsIException that has 1.955 +// |Components.results[resultName]| as the value of its 'result' property. 1.956 +function do_check_throws_nsIException(func, resultName, 1.957 + stack=Components.stack.caller, todo=false) 1.958 +{ 1.959 + let expected = Components.results[resultName]; 1.960 + if (typeof expected !== 'number') { 1.961 + do_throw("do_check_throws_nsIException requires a Components.results" + 1.962 + " property name, not " + uneval(resultName), stack); 1.963 + } 1.964 + 1.965 + let msg = ("do_check_throws_nsIException: func should throw" + 1.966 + " an nsIException whose 'result' is Components.results." + 1.967 + resultName); 1.968 + 1.969 + try { 1.970 + func(); 1.971 + } catch (ex) { 1.972 + if (!(ex instanceof Components.interfaces.nsIException) || 1.973 + ex.result !== expected) { 1.974 + do_report_result(false, msg + ", threw " + legible_exception(ex) + 1.975 + " instead", stack, todo); 1.976 + } 1.977 + 1.978 + do_report_result(true, msg, stack, todo); 1.979 + return; 1.980 + } 1.981 + 1.982 + // Call this here, not in the 'try' clause, so do_report_result's own 1.983 + // throw doesn't get caught by our 'catch' clause. 1.984 + do_report_result(false, msg + ", but returned normally", stack, todo); 1.985 +} 1.986 + 1.987 +// Produce a human-readable form of |exception|. This looks up 1.988 +// Components.results values, tries toString methods, and so on. 1.989 +function legible_exception(exception) 1.990 +{ 1.991 + switch (typeof exception) { 1.992 + case 'object': 1.993 + if (exception instanceof Components.interfaces.nsIException) { 1.994 + return "nsIException instance: " + uneval(exception.toString()); 1.995 + } 1.996 + return exception.toString(); 1.997 + 1.998 + case 'number': 1.999 + for (let name in Components.results) { 1.1000 + if (exception === Components.results[name]) { 1.1001 + return "Components.results." + name; 1.1002 + } 1.1003 + } 1.1004 + 1.1005 + // Fall through. 1.1006 + default: 1.1007 + return uneval(exception); 1.1008 + } 1.1009 +} 1.1010 + 1.1011 +function do_check_instanceof(value, constructor, 1.1012 + stack=Components.stack.caller, todo=false) { 1.1013 + do_report_result(value instanceof constructor, 1.1014 + "value should be an instance of " + constructor.name, 1.1015 + stack, todo); 1.1016 +} 1.1017 + 1.1018 +function todo_check_instanceof(value, constructor, 1.1019 + stack=Components.stack.caller) { 1.1020 + do_check_instanceof(value, constructor, stack, true); 1.1021 +} 1.1022 + 1.1023 +function do_test_pending(aName) { 1.1024 + ++_tests_pending; 1.1025 + 1.1026 + _log("test_pending", 1.1027 + {_message: "TEST-INFO | (xpcshell/head.js) | test" + 1.1028 + (aName ? " " + aName : "") + 1.1029 + " pending (" + _tests_pending + ")\n"}); 1.1030 +} 1.1031 + 1.1032 +function do_test_finished(aName) { 1.1033 + _log("test_finish", 1.1034 + {_message: "TEST-INFO | (xpcshell/head.js) | test" + 1.1035 + (aName ? " " + aName : "") + 1.1036 + " finished (" + _tests_pending + ")\n"}); 1.1037 + if (--_tests_pending == 0) 1.1038 + _do_quit(); 1.1039 +} 1.1040 + 1.1041 +function do_get_file(path, allowNonexistent) { 1.1042 + try { 1.1043 + let lf = Components.classes["@mozilla.org/file/directory_service;1"] 1.1044 + .getService(Components.interfaces.nsIProperties) 1.1045 + .get("CurWorkD", Components.interfaces.nsILocalFile); 1.1046 + 1.1047 + let bits = path.split("/"); 1.1048 + for (let i = 0; i < bits.length; i++) { 1.1049 + if (bits[i]) { 1.1050 + if (bits[i] == "..") 1.1051 + lf = lf.parent; 1.1052 + else 1.1053 + lf.append(bits[i]); 1.1054 + } 1.1055 + } 1.1056 + 1.1057 + if (!allowNonexistent && !lf.exists()) { 1.1058 + // Not using do_throw(): caller will continue. 1.1059 + _passed = false; 1.1060 + var stack = Components.stack.caller; 1.1061 + _log("test_unexpected_fail", 1.1062 + {source_file: stack.filename, 1.1063 + test_name: stack.name, 1.1064 + line_number: stack.lineNumber, 1.1065 + diagnostic: "[" + stack.name + " : " + stack.lineNumber + "] " + 1.1066 + lf.path + " does not exist\n"}); 1.1067 + } 1.1068 + 1.1069 + return lf; 1.1070 + } 1.1071 + catch (ex) { 1.1072 + do_throw(ex.toString(), Components.stack.caller); 1.1073 + } 1.1074 + 1.1075 + return null; 1.1076 +} 1.1077 + 1.1078 +// do_get_cwd() isn't exactly self-explanatory, so provide a helper 1.1079 +function do_get_cwd() { 1.1080 + return do_get_file(""); 1.1081 +} 1.1082 + 1.1083 +function do_load_manifest(path) { 1.1084 + var lf = do_get_file(path); 1.1085 + const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar; 1.1086 + do_check_true(Components.manager instanceof nsIComponentRegistrar); 1.1087 + // Previous do_check_true() is not a test check. 1.1088 + ++_falsePassedChecks; 1.1089 + Components.manager.autoRegister(lf); 1.1090 +} 1.1091 + 1.1092 +/** 1.1093 + * Parse a DOM document. 1.1094 + * 1.1095 + * @param aPath File path to the document. 1.1096 + * @param aType Content type to use in DOMParser. 1.1097 + * 1.1098 + * @return nsIDOMDocument from the file. 1.1099 + */ 1.1100 +function do_parse_document(aPath, aType) { 1.1101 + switch (aType) { 1.1102 + case "application/xhtml+xml": 1.1103 + case "application/xml": 1.1104 + case "text/xml": 1.1105 + break; 1.1106 + 1.1107 + default: 1.1108 + do_throw("type: expected application/xhtml+xml, application/xml or text/xml," + 1.1109 + " got '" + aType + "'", 1.1110 + Components.stack.caller); 1.1111 + } 1.1112 + 1.1113 + var lf = do_get_file(aPath); 1.1114 + const C_i = Components.interfaces; 1.1115 + const parserClass = "@mozilla.org/xmlextras/domparser;1"; 1.1116 + const streamClass = "@mozilla.org/network/file-input-stream;1"; 1.1117 + var stream = Components.classes[streamClass] 1.1118 + .createInstance(C_i.nsIFileInputStream); 1.1119 + stream.init(lf, -1, -1, C_i.nsIFileInputStream.CLOSE_ON_EOF); 1.1120 + var parser = Components.classes[parserClass] 1.1121 + .createInstance(C_i.nsIDOMParser); 1.1122 + var doc = parser.parseFromStream(stream, null, lf.fileSize, aType); 1.1123 + parser = null; 1.1124 + stream = null; 1.1125 + lf = null; 1.1126 + return doc; 1.1127 +} 1.1128 + 1.1129 +/** 1.1130 + * Registers a function that will run when the test harness is done running all 1.1131 + * tests. 1.1132 + * 1.1133 + * @param aFunction 1.1134 + * The function to be called when the test harness has finished running. 1.1135 + */ 1.1136 +function do_register_cleanup(aFunction) 1.1137 +{ 1.1138 + _cleanupFunctions.push(aFunction); 1.1139 +} 1.1140 + 1.1141 +/** 1.1142 + * Returns the directory for a temp dir, which is created by the 1.1143 + * test harness. Every test gets its own temp dir. 1.1144 + * 1.1145 + * @return nsILocalFile of the temporary directory 1.1146 + */ 1.1147 +function do_get_tempdir() { 1.1148 + let env = Components.classes["@mozilla.org/process/environment;1"] 1.1149 + .getService(Components.interfaces.nsIEnvironment); 1.1150 + // the python harness sets this in the environment for us 1.1151 + let path = env.get("XPCSHELL_TEST_TEMP_DIR"); 1.1152 + let file = Components.classes["@mozilla.org/file/local;1"] 1.1153 + .createInstance(Components.interfaces.nsILocalFile); 1.1154 + file.initWithPath(path); 1.1155 + return file; 1.1156 +} 1.1157 + 1.1158 +/** 1.1159 + * Returns the directory for crashreporter minidumps. 1.1160 + * 1.1161 + * @return nsILocalFile of the minidump directory 1.1162 + */ 1.1163 +function do_get_minidumpdir() { 1.1164 + let env = Components.classes["@mozilla.org/process/environment;1"] 1.1165 + .getService(Components.interfaces.nsIEnvironment); 1.1166 + // the python harness may set this in the environment for us 1.1167 + let path = env.get("XPCSHELL_MINIDUMP_DIR"); 1.1168 + if (path) { 1.1169 + let file = Components.classes["@mozilla.org/file/local;1"] 1.1170 + .createInstance(Components.interfaces.nsILocalFile); 1.1171 + file.initWithPath(path); 1.1172 + return file; 1.1173 + } else { 1.1174 + return do_get_tempdir(); 1.1175 + } 1.1176 +} 1.1177 + 1.1178 +/** 1.1179 + * Registers a directory with the profile service, 1.1180 + * and return the directory as an nsILocalFile. 1.1181 + * 1.1182 + * @return nsILocalFile of the profile directory. 1.1183 + */ 1.1184 +function do_get_profile() { 1.1185 + if (!runningInParent) { 1.1186 + _log("test_info", 1.1187 + {_message: "TEST-INFO | (xpcshell/head.js) | Ignoring profile creation from child process.\n"}); 1.1188 + 1.1189 + return null; 1.1190 + } 1.1191 + 1.1192 + if (!_profileInitialized) { 1.1193 + // Since we have a profile, we will notify profile shutdown topics at 1.1194 + // the end of the current test, to ensure correct cleanup on shutdown. 1.1195 + do_register_cleanup(function() { 1.1196 + let obsSvc = Components.classes["@mozilla.org/observer-service;1"]. 1.1197 + getService(Components.interfaces.nsIObserverService); 1.1198 + obsSvc.notifyObservers(null, "profile-change-net-teardown", null); 1.1199 + obsSvc.notifyObservers(null, "profile-change-teardown", null); 1.1200 + obsSvc.notifyObservers(null, "profile-before-change", null); 1.1201 + }); 1.1202 + } 1.1203 + 1.1204 + let env = Components.classes["@mozilla.org/process/environment;1"] 1.1205 + .getService(Components.interfaces.nsIEnvironment); 1.1206 + // the python harness sets this in the environment for us 1.1207 + let profd = env.get("XPCSHELL_TEST_PROFILE_DIR"); 1.1208 + let file = Components.classes["@mozilla.org/file/local;1"] 1.1209 + .createInstance(Components.interfaces.nsILocalFile); 1.1210 + file.initWithPath(profd); 1.1211 + 1.1212 + let dirSvc = Components.classes["@mozilla.org/file/directory_service;1"] 1.1213 + .getService(Components.interfaces.nsIProperties); 1.1214 + let provider = { 1.1215 + getFile: function(prop, persistent) { 1.1216 + persistent.value = true; 1.1217 + if (prop == "ProfD" || prop == "ProfLD" || prop == "ProfDS" || 1.1218 + prop == "ProfLDS" || prop == "TmpD") { 1.1219 + return file.clone(); 1.1220 + } 1.1221 + throw Components.results.NS_ERROR_FAILURE; 1.1222 + }, 1.1223 + QueryInterface: function(iid) { 1.1224 + if (iid.equals(Components.interfaces.nsIDirectoryServiceProvider) || 1.1225 + iid.equals(Components.interfaces.nsISupports)) { 1.1226 + return this; 1.1227 + } 1.1228 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.1229 + } 1.1230 + }; 1.1231 + dirSvc.QueryInterface(Components.interfaces.nsIDirectoryService) 1.1232 + .registerProvider(provider); 1.1233 + 1.1234 + let obsSvc = Components.classes["@mozilla.org/observer-service;1"]. 1.1235 + getService(Components.interfaces.nsIObserverService); 1.1236 + 1.1237 + // We need to update the crash events directory when the profile changes. 1.1238 + if (runningInParent && 1.1239 + "@mozilla.org/toolkit/crash-reporter;1" in Components.classes) { 1.1240 + let crashReporter = 1.1241 + Components.classes["@mozilla.org/toolkit/crash-reporter;1"] 1.1242 + .getService(Components.interfaces.nsICrashReporter); 1.1243 + crashReporter.UpdateCrashEventsDir(); 1.1244 + } 1.1245 + 1.1246 + if (!_profileInitialized) { 1.1247 + obsSvc.notifyObservers(null, "profile-do-change", "xpcshell-do-get-profile"); 1.1248 + _profileInitialized = true; 1.1249 + } 1.1250 + 1.1251 + // The methods of 'provider' will retain this scope so null out everything 1.1252 + // to avoid spurious leak reports. 1.1253 + env = null; 1.1254 + profd = null; 1.1255 + dirSvc = null; 1.1256 + provider = null; 1.1257 + obsSvc = null; 1.1258 + return file.clone(); 1.1259 +} 1.1260 + 1.1261 +/** 1.1262 + * This function loads head.js (this file) in the child process, so that all 1.1263 + * functions defined in this file (do_throw, etc) are available to subsequent 1.1264 + * sendCommand calls. It also sets various constants used by these functions. 1.1265 + * 1.1266 + * (Note that you may use sendCommand without calling this function first; you 1.1267 + * simply won't have any of the functions in this file available.) 1.1268 + */ 1.1269 +function do_load_child_test_harness() 1.1270 +{ 1.1271 + // Make sure this isn't called from child process 1.1272 + if (!runningInParent) { 1.1273 + do_throw("run_test_in_child cannot be called from child!"); 1.1274 + } 1.1275 + 1.1276 + // Allow to be called multiple times, but only run once 1.1277 + if (typeof do_load_child_test_harness.alreadyRun != "undefined") 1.1278 + return; 1.1279 + do_load_child_test_harness.alreadyRun = 1; 1.1280 + 1.1281 + _XPCSHELL_PROCESS = "parent"; 1.1282 + 1.1283 + let command = 1.1284 + "const _HEAD_JS_PATH=" + uneval(_HEAD_JS_PATH) + "; " 1.1285 + + "const _HTTPD_JS_PATH=" + uneval(_HTTPD_JS_PATH) + "; " 1.1286 + + "const _HEAD_FILES=" + uneval(_HEAD_FILES) + "; " 1.1287 + + "const _TAIL_FILES=" + uneval(_TAIL_FILES) + "; " 1.1288 + + "const _XPCSHELL_PROCESS='child';"; 1.1289 + 1.1290 + if (this._TESTING_MODULES_DIR) { 1.1291 + command += " const _TESTING_MODULES_DIR=" + uneval(_TESTING_MODULES_DIR) + ";"; 1.1292 + } 1.1293 + 1.1294 + command += " load(_HEAD_JS_PATH);"; 1.1295 + sendCommand(command); 1.1296 +} 1.1297 + 1.1298 +/** 1.1299 + * Runs an entire xpcshell unit test in a child process (rather than in chrome, 1.1300 + * which is the default). 1.1301 + * 1.1302 + * This function returns immediately, before the test has completed. 1.1303 + * 1.1304 + * @param testFile 1.1305 + * The name of the script to run. Path format same as load(). 1.1306 + * @param optionalCallback. 1.1307 + * Optional function to be called (in parent) when test on child is 1.1308 + * complete. If provided, the function must call do_test_finished(); 1.1309 + */ 1.1310 +function run_test_in_child(testFile, optionalCallback) 1.1311 +{ 1.1312 + var callback = (typeof optionalCallback == 'undefined') ? 1.1313 + do_test_finished : optionalCallback; 1.1314 + 1.1315 + do_load_child_test_harness(); 1.1316 + 1.1317 + var testPath = do_get_file(testFile).path.replace(/\\/g, "/"); 1.1318 + do_test_pending("run in child"); 1.1319 + sendCommand("_log('child_test_start', {_message: 'CHILD-TEST-STARTED'}); " 1.1320 + + "const _TEST_FILE=['" + testPath + "']; _execute_test(); " 1.1321 + + "_log('child_test_end', {_message: 'CHILD-TEST-COMPLETED'});", 1.1322 + callback); 1.1323 +} 1.1324 + 1.1325 +/** 1.1326 + * Execute a given function as soon as a particular cross-process message is received. 1.1327 + * Must be paired with do_send_remote_message or equivalent ProcessMessageManager calls. 1.1328 + */ 1.1329 +function do_await_remote_message(name, callback) 1.1330 +{ 1.1331 + var listener = { 1.1332 + receiveMessage: function(message) { 1.1333 + if (message.name == name) { 1.1334 + mm.removeMessageListener(name, listener); 1.1335 + callback(); 1.1336 + do_test_finished(); 1.1337 + } 1.1338 + } 1.1339 + }; 1.1340 + 1.1341 + var mm; 1.1342 + if (runningInParent) { 1.1343 + mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIMessageBroadcaster); 1.1344 + } else { 1.1345 + mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender); 1.1346 + } 1.1347 + do_test_pending(); 1.1348 + mm.addMessageListener(name, listener); 1.1349 +} 1.1350 + 1.1351 +/** 1.1352 + * Asynchronously send a message to all remote processes. Pairs with do_await_remote_message 1.1353 + * or equivalent ProcessMessageManager listeners. 1.1354 + */ 1.1355 +function do_send_remote_message(name) { 1.1356 + var mm; 1.1357 + var sender; 1.1358 + if (runningInParent) { 1.1359 + mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIMessageBroadcaster); 1.1360 + sender = 'broadcastAsyncMessage'; 1.1361 + } else { 1.1362 + mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender); 1.1363 + sender = 'sendAsyncMessage'; 1.1364 + } 1.1365 + mm[sender](name); 1.1366 +} 1.1367 + 1.1368 +/** 1.1369 + * Add a test function to the list of tests that are to be run asynchronously. 1.1370 + * 1.1371 + * Each test function must call run_next_test() when it's done. Test files 1.1372 + * should call run_next_test() in their run_test function to execute all 1.1373 + * async tests. 1.1374 + * 1.1375 + * @return the test function that was passed in. 1.1376 + */ 1.1377 +let _gTests = []; 1.1378 +function add_test(func) { 1.1379 + _gTests.push([false, func]); 1.1380 + return func; 1.1381 +} 1.1382 + 1.1383 +// We lazy import Task.jsm so we don't incur a run-time penalty for all tests. 1.1384 +let _Task; 1.1385 + 1.1386 +/** 1.1387 + * Add a test function which is a Task function. 1.1388 + * 1.1389 + * Task functions are functions fed into Task.jsm's Task.spawn(). They are 1.1390 + * generators that emit promises. 1.1391 + * 1.1392 + * If an exception is thrown, a do_check_* comparison fails, or if a rejected 1.1393 + * promise is yielded, the test function aborts immediately and the test is 1.1394 + * reported as a failure. 1.1395 + * 1.1396 + * Unlike add_test(), there is no need to call run_next_test(). The next test 1.1397 + * will run automatically as soon the task function is exhausted. To trigger 1.1398 + * premature (but successful) termination of the function, simply return or 1.1399 + * throw a Task.Result instance. 1.1400 + * 1.1401 + * Example usage: 1.1402 + * 1.1403 + * add_task(function test() { 1.1404 + * let result = yield Promise.resolve(true); 1.1405 + * 1.1406 + * do_check_true(result); 1.1407 + * 1.1408 + * let secondary = yield someFunctionThatReturnsAPromise(result); 1.1409 + * do_check_eq(secondary, "expected value"); 1.1410 + * }); 1.1411 + * 1.1412 + * add_task(function test_early_return() { 1.1413 + * let result = yield somethingThatReturnsAPromise(); 1.1414 + * 1.1415 + * if (!result) { 1.1416 + * // Test is ended immediately, with success. 1.1417 + * return; 1.1418 + * } 1.1419 + * 1.1420 + * do_check_eq(result, "foo"); 1.1421 + * }); 1.1422 + */ 1.1423 +function add_task(func) { 1.1424 + if (!_Task) { 1.1425 + let ns = {}; 1.1426 + _Task = Components.utils.import("resource://gre/modules/Task.jsm", ns).Task; 1.1427 + } 1.1428 + 1.1429 + _gTests.push([true, func]); 1.1430 +} 1.1431 + 1.1432 +/** 1.1433 + * Runs the next test function from the list of async tests. 1.1434 + */ 1.1435 +let _gRunningTest = null; 1.1436 +let _gTestIndex = 0; // The index of the currently running test. 1.1437 +let _gTaskRunning = false; 1.1438 +function run_next_test() 1.1439 +{ 1.1440 + if (_gTaskRunning) { 1.1441 + throw new Error("run_next_test() called from an add_task() test function. " + 1.1442 + "run_next_test() should not be called from inside add_task() " + 1.1443 + "under any circumstances!"); 1.1444 + } 1.1445 + 1.1446 + function _run_next_test() 1.1447 + { 1.1448 + if (_gTestIndex < _gTests.length) { 1.1449 + // Flush uncaught errors as early and often as possible. 1.1450 + _Promise.Debugging.flushUncaughtErrors(); 1.1451 + let _isTask; 1.1452 + [_isTask, _gRunningTest] = _gTests[_gTestIndex++]; 1.1453 + print("TEST-INFO | " + _TEST_FILE + " | Starting " + _gRunningTest.name); 1.1454 + do_test_pending(_gRunningTest.name); 1.1455 + 1.1456 + if (_isTask) { 1.1457 + _gTaskRunning = true; 1.1458 + _Task.spawn(_gRunningTest).then( 1.1459 + () => { _gTaskRunning = false; run_next_test(); }, 1.1460 + (ex) => { _gTaskRunning = false; do_report_unexpected_exception(ex); } 1.1461 + ); 1.1462 + } else { 1.1463 + // Exceptions do not kill asynchronous tests, so they'll time out. 1.1464 + try { 1.1465 + _gRunningTest(); 1.1466 + } catch (e) { 1.1467 + do_throw(e); 1.1468 + } 1.1469 + } 1.1470 + } 1.1471 + } 1.1472 + 1.1473 + // For sane stacks during failures, we execute this code soon, but not now. 1.1474 + // We do this now, before we call do_test_finished(), to ensure the pending 1.1475 + // counter (_tests_pending) never reaches 0 while we still have tests to run 1.1476 + // (do_execute_soon bumps that counter). 1.1477 + do_execute_soon(_run_next_test, "run_next_test " + _gTestIndex); 1.1478 + 1.1479 + if (_gRunningTest !== null) { 1.1480 + // Close the previous test do_test_pending call. 1.1481 + do_test_finished(_gRunningTest.name); 1.1482 + } 1.1483 +} 1.1484 + 1.1485 +try { 1.1486 + if (runningInParent) { 1.1487 + // Always use network provider for geolocation tests 1.1488 + // so we bypass the OSX dialog raised by the corelocation provider 1.1489 + let prefs = Components.classes["@mozilla.org/preferences-service;1"] 1.1490 + .getService(Components.interfaces.nsIPrefBranch); 1.1491 + 1.1492 + prefs.setBoolPref("geo.provider.testing", true); 1.1493 + } 1.1494 +} catch (e) { }