testing/xpcshell/head.js

changeset 0
6474c204b198
     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) { }

mercurial