mobile/android/base/tests/robocop_head.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/tests/robocop_head.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1233 @@
     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 +const _XPCSHELL_TIMEOUT_MS = 5 * 60 * 1000;
    1.17 +
    1.18 +var _quit = false;
    1.19 +var _passed = true;
    1.20 +var _tests_pending = 0;
    1.21 +var _passedChecks = 0, _falsePassedChecks = 0;
    1.22 +var _todoChecks = 0;
    1.23 +var _cleanupFunctions = [];
    1.24 +var _pendingTimers = [];
    1.25 +var _profileInitialized = false;
    1.26 +
    1.27 +function _dump(str) {
    1.28 +  let start = /^TEST-/.test(str) ? "\n" : "";
    1.29 +  if (typeof _XPCSHELL_PROCESS == "undefined") {
    1.30 +    dump(start + str);
    1.31 +  } else {
    1.32 +    dump(start + _XPCSHELL_PROCESS + ": " + str);
    1.33 +  }
    1.34 +}
    1.35 +
    1.36 +// Disable automatic network detection, so tests work correctly when
    1.37 +// not connected to a network.
    1.38 +let (ios = Components.classes["@mozilla.org/network/io-service;1"]
    1.39 +           .getService(Components.interfaces.nsIIOService2)) {
    1.40 +  ios.manageOfflineStatus = false;
    1.41 +  ios.offline = false;
    1.42 +}
    1.43 +
    1.44 +// Determine if we're running on parent or child
    1.45 +let runningInParent = true;
    1.46 +try {
    1.47 +  runningInParent = Components.classes["@mozilla.org/xre/runtime;1"].
    1.48 +                    getService(Components.interfaces.nsIXULRuntime).processType
    1.49 +                    == Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
    1.50 +}
    1.51 +catch (e) { }
    1.52 +
    1.53 +try {
    1.54 +  if (runningInParent) {
    1.55 +    let prefs = Components.classes["@mozilla.org/preferences-service;1"]
    1.56 +                .getService(Components.interfaces.nsIPrefBranch);
    1.57 +
    1.58 +    // disable necko IPC security checks for xpcshell, as they lack the
    1.59 +    // docshells needed to pass them
    1.60 +    prefs.setBoolPref("network.disable.ipc.security", true);
    1.61 +
    1.62 +    // Disable IPv6 lookups for 'localhost' on windows.
    1.63 +    if ("@mozilla.org/windows-registry-key;1" in Components.classes) {
    1.64 +      prefs.setCharPref("network.dns.ipv4OnlyDomains", "localhost");
    1.65 +    }
    1.66 +  }
    1.67 +}
    1.68 +catch (e) { }
    1.69 +
    1.70 +// Enable crash reporting, if possible
    1.71 +// We rely on the Python harness to set MOZ_CRASHREPORTER_NO_REPORT
    1.72 +// and handle checking for minidumps.
    1.73 +// Note that if we're in a child process, we don't want to init the
    1.74 +// crashreporter component.
    1.75 +try { // nsIXULRuntime is not available in some configurations.
    1.76 +  if (runningInParent &&
    1.77 +      "@mozilla.org/toolkit/crash-reporter;1" in Components.classes) {
    1.78 +    // Remember to update </toolkit/crashreporter/test/unit/test_crashreporter.js>
    1.79 +    // too if you change this initial setting.
    1.80 +    let (crashReporter =
    1.81 +          Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
    1.82 +          .getService(Components.interfaces.nsICrashReporter)) {
    1.83 +      crashReporter.enabled = true;
    1.84 +      crashReporter.minidumpPath = do_get_cwd();
    1.85 +    }
    1.86 +  }
    1.87 +}
    1.88 +catch (e) { }
    1.89 +
    1.90 +/**
    1.91 + * Date.now() is not necessarily monotonically increasing (insert sob story
    1.92 + * about times not being the right tool to use for measuring intervals of time,
    1.93 + * robarnold can tell all), so be wary of error by erring by at least
    1.94 + * _timerFuzz ms.
    1.95 + */
    1.96 +const _timerFuzz = 15;
    1.97 +
    1.98 +function _Timer(func, delay) {
    1.99 +  delay = Number(delay);
   1.100 +  if (delay < 0)
   1.101 +    do_throw("do_timeout() delay must be nonnegative");
   1.102 +
   1.103 +  if (typeof func !== "function")
   1.104 +    do_throw("string callbacks no longer accepted; use a function!");
   1.105 +
   1.106 +  this._func = func;
   1.107 +  this._start = Date.now();
   1.108 +  this._delay = delay;
   1.109 +
   1.110 +  var timer = Components.classes["@mozilla.org/timer;1"]
   1.111 +                        .createInstance(Components.interfaces.nsITimer);
   1.112 +  timer.initWithCallback(this, delay + _timerFuzz, timer.TYPE_ONE_SHOT);
   1.113 +
   1.114 +  // Keep timer alive until it fires
   1.115 +  _pendingTimers.push(timer);
   1.116 +}
   1.117 +_Timer.prototype = {
   1.118 +  QueryInterface: function(iid) {
   1.119 +    if (iid.equals(Components.interfaces.nsITimerCallback) ||
   1.120 +        iid.equals(Components.interfaces.nsISupports))
   1.121 +      return this;
   1.122 +
   1.123 +    throw Components.results.NS_ERROR_NO_INTERFACE;
   1.124 +  },
   1.125 +
   1.126 +  notify: function(timer) {
   1.127 +    _pendingTimers.splice(_pendingTimers.indexOf(timer), 1);
   1.128 +
   1.129 +    // The current nsITimer implementation can undershoot, but even if it
   1.130 +    // couldn't, paranoia is probably a virtue here given the potential for
   1.131 +    // random orange on tinderboxen.
   1.132 +    var end = Date.now();
   1.133 +    var elapsed = end - this._start;
   1.134 +    if (elapsed >= this._delay) {
   1.135 +      try {
   1.136 +        this._func.call(null);
   1.137 +      } catch (e) {
   1.138 +        do_throw("exception thrown from do_timeout callback: " + e);
   1.139 +      }
   1.140 +      return;
   1.141 +    }
   1.142 +
   1.143 +    // Timer undershot, retry with a little overshoot to try to avoid more
   1.144 +    // undershoots.
   1.145 +    var newDelay = this._delay - elapsed;
   1.146 +    do_timeout(newDelay, this._func);
   1.147 +  }
   1.148 +};
   1.149 +
   1.150 +function _do_main() {
   1.151 +  if (_quit)
   1.152 +    return;
   1.153 +
   1.154 +  _dump("TEST-INFO | (xpcshell/head.js) | running event loop\n");
   1.155 +
   1.156 +  var thr = Components.classes["@mozilla.org/thread-manager;1"]
   1.157 +                      .getService().currentThread;
   1.158 +
   1.159 +  while (!_quit)
   1.160 +    thr.processNextEvent(true);
   1.161 +
   1.162 +  while (thr.hasPendingEvents())
   1.163 +    thr.processNextEvent(true);
   1.164 +}
   1.165 +
   1.166 +function _do_quit() {
   1.167 +  _dump("TEST-INFO | (xpcshell/head.js) | exiting test\n");
   1.168 +
   1.169 +  _quit = true;
   1.170 +}
   1.171 +
   1.172 +function _dump_exception_stack(stack) {
   1.173 +  stack.split("\n").forEach(function(frame) {
   1.174 +    if (!frame)
   1.175 +      return;
   1.176 +    // frame is of the form "fname(args)@file:line"
   1.177 +    let frame_regexp = new RegExp("(.*)\\(.*\\)@(.*):(\\d*)", "g");
   1.178 +    let parts = frame_regexp.exec(frame);
   1.179 +    if (parts)
   1.180 +        dump("JS frame :: " + parts[2] + " :: " + (parts[1] ? parts[1] : "anonymous")
   1.181 +             + " :: line " + parts[3] + "\n");
   1.182 +    else /* Could be a -e (command line string) style location. */
   1.183 +        dump("JS frame :: " + frame + "\n");
   1.184 +  });
   1.185 +}
   1.186 +
   1.187 +/**
   1.188 + * Overrides idleService with a mock.  Idle is commonly used for maintenance
   1.189 + * tasks, thus if a test uses a service that requires the idle service, it will
   1.190 + * start handling them.
   1.191 + * This behaviour would cause random failures and slowdown tests execution,
   1.192 + * for example by running database vacuum or cleanups for each test.
   1.193 + *
   1.194 + * @note Idle service is overridden by default.  If a test requires it, it will
   1.195 + *       have to call do_get_idle() function at least once before use.
   1.196 + */
   1.197 +var _fakeIdleService = {
   1.198 +  get registrar() {
   1.199 +    delete this.registrar;
   1.200 +    return this.registrar =
   1.201 +      Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
   1.202 +  },
   1.203 +  contractID: "@mozilla.org/widget/idleservice;1",
   1.204 +  get CID() this.registrar.contractIDToCID(this.contractID),
   1.205 +
   1.206 +  activate: function FIS_activate()
   1.207 +  {
   1.208 +    if (!this.originalFactory) {
   1.209 +      // Save original factory.
   1.210 +      this.originalFactory =
   1.211 +        Components.manager.getClassObject(Components.classes[this.contractID],
   1.212 +                                          Components.interfaces.nsIFactory);
   1.213 +      // Unregister original factory.
   1.214 +      this.registrar.unregisterFactory(this.CID, this.originalFactory);
   1.215 +      // Replace with the mock.
   1.216 +      this.registrar.registerFactory(this.CID, "Fake Idle Service",
   1.217 +                                     this.contractID, this.factory
   1.218 +      );
   1.219 +    }
   1.220 +  },
   1.221 +
   1.222 +  deactivate: function FIS_deactivate()
   1.223 +  {
   1.224 +    if (this.originalFactory) {
   1.225 +      // Unregister the mock.
   1.226 +      this.registrar.unregisterFactory(this.CID, this.factory);
   1.227 +      // Restore original factory.
   1.228 +      this.registrar.registerFactory(this.CID, "Idle Service",
   1.229 +                                     this.contractID, this.originalFactory);
   1.230 +      delete this.originalFactory;
   1.231 +    }
   1.232 +  },
   1.233 +
   1.234 +  factory: {
   1.235 +    // nsIFactory
   1.236 +    createInstance: function (aOuter, aIID)
   1.237 +    {
   1.238 +      if (aOuter) {
   1.239 +        throw Components.results.NS_ERROR_NO_AGGREGATION;
   1.240 +      }
   1.241 +      return _fakeIdleService.QueryInterface(aIID);
   1.242 +    },
   1.243 +    lockFactory: function (aLock) {
   1.244 +      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   1.245 +    },
   1.246 +    QueryInterface: function(aIID) {
   1.247 +      if (aIID.equals(Components.interfaces.nsIFactory) ||
   1.248 +          aIID.equals(Components.interfaces.nsISupports)) {
   1.249 +        return this;
   1.250 +      }
   1.251 +      throw Components.results.NS_ERROR_NO_INTERFACE;
   1.252 +    }
   1.253 +  },
   1.254 +
   1.255 +  // nsIIdleService
   1.256 +  get idleTime() 0,
   1.257 +  addIdleObserver: function () {},
   1.258 +  removeIdleObserver: function () {},
   1.259 +
   1.260 +  QueryInterface: function(aIID) {
   1.261 +    // Useful for testing purposes, see test_get_idle.js.
   1.262 +    if (aIID.equals(Components.interfaces.nsIFactory)) {
   1.263 +      return this.factory;
   1.264 +    }
   1.265 +    if (aIID.equals(Components.interfaces.nsIIdleService) ||
   1.266 +        aIID.equals(Components.interfaces.nsISupports)) {
   1.267 +      return this;
   1.268 +    }
   1.269 +    throw Components.results.NS_ERROR_NO_INTERFACE;
   1.270 +  }
   1.271 +}
   1.272 +
   1.273 +/**
   1.274 + * Restores the idle service factory if needed and returns the service's handle.
   1.275 + * @return A handle to the idle service.
   1.276 + */
   1.277 +function do_get_idle() {
   1.278 +  _fakeIdleService.deactivate();
   1.279 +  return Components.classes[_fakeIdleService.contractID]
   1.280 +                   .getService(Components.interfaces.nsIIdleService);
   1.281 +}
   1.282 +
   1.283 +// Map resource://test/ to current working directory and
   1.284 +// resource://testing-common/ to the shared test modules directory.
   1.285 +function _register_protocol_handlers() {
   1.286 +  let (ios = Components.classes["@mozilla.org/network/io-service;1"]
   1.287 +             .getService(Components.interfaces.nsIIOService)) {
   1.288 +    let protocolHandler =
   1.289 +      ios.getProtocolHandler("resource")
   1.290 +         .QueryInterface(Components.interfaces.nsIResProtocolHandler);
   1.291 +    let curDirURI = ios.newFileURI(do_get_cwd());
   1.292 +    protocolHandler.setSubstitution("test", curDirURI);
   1.293 +
   1.294 +    if (this._TESTING_MODULES_DIR) {
   1.295 +      let modulesFile = Components.classes["@mozilla.org/file/local;1"].
   1.296 +                        createInstance(Components.interfaces.nsILocalFile);
   1.297 +      modulesFile.initWithPath(_TESTING_MODULES_DIR);
   1.298 +
   1.299 +      if (!modulesFile.exists()) {
   1.300 +        throw new Error("Specified modules directory does not exist: " +
   1.301 +                        _TESTING_MODULES_DIR);
   1.302 +      }
   1.303 +
   1.304 +      if (!modulesFile.isDirectory()) {
   1.305 +        throw new Error("Specified modules directory is not a directory: " +
   1.306 +                        _TESTING_MODULES_DIR);
   1.307 +      }
   1.308 +
   1.309 +      let modulesURI = ios.newFileURI(modulesFile);
   1.310 +
   1.311 +      protocolHandler.setSubstitution("testing-common", modulesURI);
   1.312 +    }
   1.313 +  }
   1.314 +}
   1.315 +
   1.316 +function _execute_test() {
   1.317 +  _register_protocol_handlers();
   1.318 +
   1.319 +  // Override idle service by default.
   1.320 +  // Call do_get_idle() to restore the factory and get the service.
   1.321 +  _fakeIdleService.activate();
   1.322 +
   1.323 +  // Terminate asynchronous tests when a global timeout occurs.
   1.324 +  do_timeout(_XPCSHELL_TIMEOUT_MS, function _do_main_timeout() {
   1.325 +    try {
   1.326 +      do_throw("test timed out");
   1.327 +    } catch (e if e == Components.results.NS_ERROR_ABORT) {
   1.328 +      // We don't want do_timeout to report the do_throw exception again.
   1.329 +    }
   1.330 +  });
   1.331 +
   1.332 +  // _HEAD_FILES is dynamically defined by <runxpcshelltests.py>.
   1.333 +  _load_files(_HEAD_FILES);
   1.334 +  // _TEST_FILE is dynamically defined by <runxpcshelltests.py>.
   1.335 +  _load_files(_TEST_FILE);
   1.336 +
   1.337 +  try {
   1.338 +    do_test_pending();
   1.339 +    run_test();
   1.340 +    do_test_finished();
   1.341 +    _do_main();
   1.342 +  } catch (e) {
   1.343 +    _passed = false;
   1.344 +    // do_check failures are already logged and set _quit to true and throw
   1.345 +    // NS_ERROR_ABORT. If both of those are true it is likely this exception
   1.346 +    // has already been logged so there is no need to log it again. It's
   1.347 +    // possible that this will mask an NS_ERROR_ABORT that happens after a
   1.348 +    // do_check failure though.
   1.349 +    if (!_quit || e != Components.results.NS_ERROR_ABORT) {
   1.350 +      msg = "TEST-UNEXPECTED-FAIL | ";
   1.351 +      if (e.fileName) {
   1.352 +        msg += e.fileName;
   1.353 +        if (e.lineNumber) {
   1.354 +          msg += ":" + e.lineNumber;
   1.355 +        }
   1.356 +      } else {
   1.357 +        msg += "xpcshell/head.js";
   1.358 +      }
   1.359 +      msg += " | " + e;
   1.360 +      if (e.stack) {
   1.361 +        _dump(msg + " - See following stack:\n");
   1.362 +        _dump_exception_stack(e.stack);
   1.363 +      }
   1.364 +      else {
   1.365 +        _dump(msg + "\n");
   1.366 +      }
   1.367 +    }
   1.368 +  }
   1.369 +
   1.370 +  // _TAIL_FILES is dynamically defined by <runxpcshelltests.py>.
   1.371 +  _load_files(_TAIL_FILES);
   1.372 +
   1.373 +  // Execute all of our cleanup functions.
   1.374 +  var func;
   1.375 +  while ((func = _cleanupFunctions.pop()))
   1.376 +    func();
   1.377 +
   1.378 +  // Restore idle service to avoid leaks.
   1.379 +  _fakeIdleService.deactivate();
   1.380 +
   1.381 +  if (!_passed)
   1.382 +    return;
   1.383 +
   1.384 +  var truePassedChecks = _passedChecks - _falsePassedChecks;
   1.385 +  if (truePassedChecks > 0) {
   1.386 +    _dump("TEST-PASS | (xpcshell/head.js) | " + truePassedChecks + " (+ " +
   1.387 +            _falsePassedChecks + ") check(s) passed\n");
   1.388 +    _dump("TEST-INFO | (xpcshell/head.js) | " + _todoChecks +
   1.389 +            " check(s) todo\n");
   1.390 +  } else {
   1.391 +    // ToDo: switch to TEST-UNEXPECTED-FAIL when all tests have been updated. (Bug 496443)
   1.392 +    _dump("TEST-INFO | (xpcshell/head.js) | No (+ " + _falsePassedChecks + ") checks actually run\n");
   1.393 +  }
   1.394 +}
   1.395 +
   1.396 +/**
   1.397 + * Loads files.
   1.398 + *
   1.399 + * @param aFiles Array of files to load.
   1.400 + */
   1.401 +function _load_files(aFiles) {
   1.402 +  function loadTailFile(element, index, array) {
   1.403 +    load(element);
   1.404 +  }
   1.405 +
   1.406 +  aFiles.forEach(loadTailFile);
   1.407 +}
   1.408 +
   1.409 +
   1.410 +/************** Functions to be used from the tests **************/
   1.411 +
   1.412 +/**
   1.413 + * Prints a message to the output log.
   1.414 + */
   1.415 +function do_print(msg) {
   1.416 +  var caller_stack = Components.stack.caller;
   1.417 +  _dump("TEST-INFO | " + caller_stack.filename + " | " + msg + "\n");
   1.418 +}
   1.419 +
   1.420 +/**
   1.421 + * Calls the given function at least the specified number of milliseconds later.
   1.422 + * The callback will not undershoot the given time, but it might overshoot --
   1.423 + * don't expect precision!
   1.424 + *
   1.425 + * @param delay : uint
   1.426 + *   the number of milliseconds to delay
   1.427 + * @param callback : function() : void
   1.428 + *   the function to call
   1.429 + */
   1.430 +function do_timeout(delay, func) {
   1.431 +  new _Timer(func, Number(delay));
   1.432 +}
   1.433 +
   1.434 +function do_execute_soon(callback) {
   1.435 +  do_test_pending();
   1.436 +  var tm = Components.classes["@mozilla.org/thread-manager;1"]
   1.437 +                     .getService(Components.interfaces.nsIThreadManager);
   1.438 +
   1.439 +  tm.mainThread.dispatch({
   1.440 +    run: function() {
   1.441 +      try {
   1.442 +        callback();
   1.443 +      } catch (e) {
   1.444 +        // do_check failures are already logged and set _quit to true and throw
   1.445 +        // NS_ERROR_ABORT. If both of those are true it is likely this exception
   1.446 +        // has already been logged so there is no need to log it again. It's
   1.447 +        // possible that this will mask an NS_ERROR_ABORT that happens after a
   1.448 +        // do_check failure though.
   1.449 +        if (!_quit || e != Components.results.NS_ERROR_ABORT) {
   1.450 +          _dump("TEST-UNEXPECTED-FAIL | (xpcshell/head.js) | " + e);
   1.451 +          if (e.stack) {
   1.452 +            dump(" - See following stack:\n");
   1.453 +            _dump_exception_stack(e.stack);
   1.454 +          }
   1.455 +          else {
   1.456 +            dump("\n");
   1.457 +          }
   1.458 +          _do_quit();
   1.459 +        }
   1.460 +      }
   1.461 +      finally {
   1.462 +        do_test_finished();
   1.463 +      }
   1.464 +    }
   1.465 +  }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
   1.466 +}
   1.467 +
   1.468 +function do_throw(text, stack) {
   1.469 +  if (!stack)
   1.470 +    stack = Components.stack.caller;
   1.471 +
   1.472 +  _passed = false;
   1.473 +  _dump("TEST-UNEXPECTED-FAIL | " + stack.filename + " | " + text +
   1.474 +        " - See following stack:\n");
   1.475 +  var frame = Components.stack;
   1.476 +  while (frame != null) {
   1.477 +    _dump(frame + "\n");
   1.478 +    frame = frame.caller;
   1.479 +  }
   1.480 +
   1.481 +  _do_quit();
   1.482 +  throw Components.results.NS_ERROR_ABORT;
   1.483 +}
   1.484 +
   1.485 +function do_throw_todo(text, stack) {
   1.486 +  if (!stack)
   1.487 +    stack = Components.stack.caller;
   1.488 +
   1.489 +  _passed = false;
   1.490 +  _dump("TEST-UNEXPECTED-PASS | " + stack.filename + " | " + text +
   1.491 +        " - See following stack:\n");
   1.492 +  var frame = Components.stack;
   1.493 +  while (frame != null) {
   1.494 +    _dump(frame + "\n");
   1.495 +    frame = frame.caller;
   1.496 +  }
   1.497 +
   1.498 +  _do_quit();
   1.499 +  throw Components.results.NS_ERROR_ABORT;
   1.500 +}
   1.501 +
   1.502 +function do_report_unexpected_exception(ex, text) {
   1.503 +  var caller_stack = Components.stack.caller;
   1.504 +  text = text ? text + " - " : "";
   1.505 +
   1.506 +  _passed = false;
   1.507 +  _dump("TEST-UNEXPECTED-FAIL | " + caller_stack.filename + " | " + text +
   1.508 +        "Unexpected exception " + ex + ", see following stack:\n" + ex.stack +
   1.509 +        "\n");
   1.510 +
   1.511 +  _do_quit();
   1.512 +  throw Components.results.NS_ERROR_ABORT;
   1.513 +}
   1.514 +
   1.515 +function do_note_exception(ex, text) {
   1.516 +  var caller_stack = Components.stack.caller;
   1.517 +  text = text ? text + " - " : "";
   1.518 +
   1.519 +  _dump("TEST-INFO | " + caller_stack.filename + " | " + text +
   1.520 +        "Swallowed exception " + ex + ", see following stack:\n" + ex.stack +
   1.521 +        "\n");
   1.522 +}
   1.523 +
   1.524 +function _do_check_neq(left, right, stack, todo) {
   1.525 +  if (!stack)
   1.526 +    stack = Components.stack.caller;
   1.527 +
   1.528 +  var text = left + " != " + right;
   1.529 +  if (left == right) {
   1.530 +    if (!todo) {
   1.531 +      do_throw(text, stack);
   1.532 +    } else {
   1.533 +      ++_todoChecks;
   1.534 +      _dump("TEST-KNOWN-FAIL | " + stack.filename + " | [" + stack.name +
   1.535 +            " : " + stack.lineNumber + "] " + text +"\n");
   1.536 +    }
   1.537 +  } else {
   1.538 +    if (!todo) {
   1.539 +      ++_passedChecks;
   1.540 +      _dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " +
   1.541 +            stack.lineNumber + "] " + text + "\n");
   1.542 +    } else {
   1.543 +      do_throw_todo(text, stack);
   1.544 +    }
   1.545 +  }
   1.546 +}
   1.547 +
   1.548 +function do_check_neq(left, right, stack) {
   1.549 +  if (!stack)
   1.550 +    stack = Components.stack.caller;
   1.551 +
   1.552 +  _do_check_neq(left, right, stack, false);
   1.553 +}
   1.554 +
   1.555 +function todo_check_neq(left, right, stack) {
   1.556 +  if (!stack)
   1.557 +      stack = Components.stack.caller;
   1.558 +
   1.559 +  _do_check_neq(left, right, stack, true);
   1.560 +}
   1.561 +
   1.562 +function do_report_result(passed, text, stack, todo) {
   1.563 +  if (passed) {
   1.564 +    if (todo) {
   1.565 +      do_throw_todo(text, stack);
   1.566 +    } else {
   1.567 +      ++_passedChecks;
   1.568 +      _dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " +
   1.569 +            stack.lineNumber + "] " + text + "\n");
   1.570 +    }
   1.571 +  } else {
   1.572 +    if (todo) {
   1.573 +      ++_todoChecks;
   1.574 +      _dump("TEST-KNOWN-FAIL | " + stack.filename + " | [" + stack.name +
   1.575 +            " : " + stack.lineNumber + "] " + text +"\n");
   1.576 +    } else {
   1.577 +      do_throw(text, stack);
   1.578 +    }
   1.579 +  }
   1.580 +}
   1.581 +
   1.582 +function _do_check_eq(left, right, stack, todo) {
   1.583 +  if (!stack)
   1.584 +    stack = Components.stack.caller;
   1.585 +
   1.586 +  var text = left + " == " + right;
   1.587 +  do_report_result(left == right, text, stack, todo);
   1.588 +}
   1.589 +
   1.590 +function do_check_eq(left, right, stack) {
   1.591 +  if (!stack)
   1.592 +    stack = Components.stack.caller;
   1.593 +
   1.594 +  _do_check_eq(left, right, stack, false);
   1.595 +}
   1.596 +
   1.597 +function todo_check_eq(left, right, stack) {
   1.598 +  if (!stack)
   1.599 +      stack = Components.stack.caller;
   1.600 +
   1.601 +  _do_check_eq(left, right, stack, true);
   1.602 +}
   1.603 +
   1.604 +function do_check_true(condition, stack) {
   1.605 +  if (!stack)
   1.606 +    stack = Components.stack.caller;
   1.607 +
   1.608 +  do_check_eq(condition, true, stack);
   1.609 +}
   1.610 +
   1.611 +function todo_check_true(condition, stack) {
   1.612 +  if (!stack)
   1.613 +    stack = Components.stack.caller;
   1.614 +
   1.615 +  todo_check_eq(condition, true, stack);
   1.616 +}
   1.617 +
   1.618 +function do_check_false(condition, stack) {
   1.619 +  if (!stack)
   1.620 +    stack = Components.stack.caller;
   1.621 +
   1.622 +  do_check_eq(condition, false, stack);
   1.623 +}
   1.624 +
   1.625 +function todo_check_false(condition, stack) {
   1.626 +  if (!stack)
   1.627 +    stack = Components.stack.caller;
   1.628 +
   1.629 +  todo_check_eq(condition, false, stack);
   1.630 +}
   1.631 +
   1.632 +function do_check_null(condition, stack=Components.stack.caller) {
   1.633 +  do_check_eq(condition, null, stack);
   1.634 +}
   1.635 +
   1.636 +function todo_check_null(condition, stack=Components.stack.caller) {
   1.637 +  todo_check_eq(condition, null, stack);
   1.638 +}
   1.639 +
   1.640 +/**
   1.641 + * Check that |value| matches |pattern|.
   1.642 + *
   1.643 + * A |value| matches a pattern |pattern| if any one of the following is true:
   1.644 + *
   1.645 + * - |value| and |pattern| are both objects; |pattern|'s enumerable
   1.646 + *   properties' values are valid patterns; and for each enumerable
   1.647 + *   property |p| of |pattern|, plus 'length' if present at all, |value|
   1.648 + *   has a property |p| whose value matches |pattern.p|. Note that if |j|
   1.649 + *   has other properties not present in |p|, |j| may still match |p|.
   1.650 + *
   1.651 + * - |value| and |pattern| are equal string, numeric, or boolean literals
   1.652 + *
   1.653 + * - |pattern| is |undefined| (this is a wildcard pattern)
   1.654 + *
   1.655 + * - typeof |pattern| == "function", and |pattern(value)| is true.
   1.656 + *
   1.657 + * For example:
   1.658 + *
   1.659 + * do_check_matches({x:1}, {x:1})       // pass
   1.660 + * do_check_matches({x:1}, {})          // fail: all pattern props required
   1.661 + * do_check_matches({x:1}, {x:2})       // fail: values must match
   1.662 + * do_check_matches({x:1}, {x:1, y:2})  // pass: extra props tolerated
   1.663 + *
   1.664 + * // Property order is irrelevant.
   1.665 + * do_check_matches({x:"foo", y:"bar"}, {y:"bar", x:"foo"}) // pass
   1.666 + *
   1.667 + * do_check_matches({x:undefined}, {x:1}) // pass: 'undefined' is wildcard
   1.668 + * do_check_matches({x:undefined}, {x:2})
   1.669 + * do_check_matches({x:undefined}, {y:2}) // fail: 'x' must still be there
   1.670 + *
   1.671 + * // Patterns nest.
   1.672 + * do_check_matches({a:1, b:{c:2,d:undefined}}, {a:1, b:{c:2,d:3}})
   1.673 + *
   1.674 + * // 'length' property counts, even if non-enumerable.
   1.675 + * do_check_matches([3,4,5], [3,4,5])     // pass
   1.676 + * do_check_matches([3,4,5], [3,5,5])     // fail; value doesn't match
   1.677 + * do_check_matches([3,4,5], [3,4,5,6])   // fail; length doesn't match
   1.678 + *
   1.679 + * // functions in patterns get applied.
   1.680 + * do_check_matches({foo:function (v) v.length == 2}, {foo:"hi"}) // pass
   1.681 + * do_check_matches({foo:function (v) v.length == 2}, {bar:"hi"}) // fail
   1.682 + * do_check_matches({foo:function (v) v.length == 2}, {foo:"hello"}) // fail
   1.683 + *
   1.684 + * // We don't check constructors, prototypes, or classes. However, if
   1.685 + * // pattern has a 'length' property, we require values to match that as
   1.686 + * // well, even if 'length' is non-enumerable in the pattern. So arrays
   1.687 + * // are useful as patterns.
   1.688 + * do_check_matches({0:0, 1:1, length:2}, [0,1])  // pass
   1.689 + * do_check_matches({0:1}, [1,2])                 // pass
   1.690 + * do_check_matches([0], {0:0, length:1})         // pass
   1.691 + *
   1.692 + * Notes:
   1.693 + *
   1.694 + * The 'length' hack gives us reasonably intuitive handling of arrays.
   1.695 + *
   1.696 + * This is not a tight pattern-matcher; it's only good for checking data
   1.697 + * from well-behaved sources. For example:
   1.698 + * - By default, we don't mind values having extra properties.
   1.699 + * - We don't check for proxies or getters.
   1.700 + * - We don't check the prototype chain.
   1.701 + * However, if you know the values are, say, JSON, which is pretty
   1.702 + * well-behaved, and if you want to tolerate additional properties
   1.703 + * appearing on the JSON for backward-compatibility, then do_check_matches
   1.704 + * is ideal. If you do want to be more careful, you can use function
   1.705 + * patterns to implement more stringent checks.
   1.706 + */
   1.707 +function do_check_matches(pattern, value, stack=Components.stack.caller, todo=false) {
   1.708 +  var matcher = pattern_matcher(pattern);
   1.709 +  var text = "VALUE: " + uneval(value) + "\nPATTERN: " + uneval(pattern) + "\n";
   1.710 +  var diagnosis = []
   1.711 +  if (matcher(value, diagnosis)) {
   1.712 +    do_report_result(true, "value matches pattern:\n" + text, stack, todo);
   1.713 +  } else {
   1.714 +    text = ("value doesn't match pattern:\n" +
   1.715 +            text +
   1.716 +            "DIAGNOSIS: " +
   1.717 +            format_pattern_match_failure(diagnosis[0]) + "\n");
   1.718 +    do_report_result(false, text, stack, todo);
   1.719 +  }
   1.720 +}
   1.721 +
   1.722 +function todo_check_matches(pattern, value, stack=Components.stack.caller) {
   1.723 +  do_check_matches(pattern, value, stack, true);
   1.724 +}
   1.725 +
   1.726 +// Return a pattern-matching function of one argument, |value|, that
   1.727 +// returns true if |value| matches |pattern|.
   1.728 +//
   1.729 +// If the pattern doesn't match, and the pattern-matching function was
   1.730 +// passed its optional |diagnosis| argument, the pattern-matching function
   1.731 +// sets |diagnosis|'s '0' property to a JSON-ish description of the portion
   1.732 +// of the pattern that didn't match, which can be formatted legibly by
   1.733 +// format_pattern_match_failure.
   1.734 +function pattern_matcher(pattern) {
   1.735 +  function explain(diagnosis, reason) {
   1.736 +    if (diagnosis) {
   1.737 +      diagnosis[0] = reason;
   1.738 +    }
   1.739 +    return false;
   1.740 +  }
   1.741 +  if (typeof pattern == "function") {
   1.742 +    return pattern;
   1.743 +  } else if (typeof pattern == "object" && pattern) {
   1.744 +    var matchers = [[p, pattern_matcher(pattern[p])] for (p in pattern)];
   1.745 +    // Kludge: include 'length', if not enumerable. (If it is enumerable,
   1.746 +    // we picked it up in the array comprehension, above.
   1.747 +    ld = Object.getOwnPropertyDescriptor(pattern, 'length');
   1.748 +    if (ld && !ld.enumerable) {
   1.749 +      matchers.push(['length', pattern_matcher(pattern.length)])
   1.750 +    }
   1.751 +    return function (value, diagnosis) {
   1.752 +      if (!(value && typeof value == "object")) {
   1.753 +        return explain(diagnosis, "value not object");
   1.754 +      }
   1.755 +      for (let [p, m] of matchers) {
   1.756 +        var element_diagnosis = [];
   1.757 +        if (!(p in value && m(value[p], element_diagnosis))) {
   1.758 +          return explain(diagnosis, { property:p,
   1.759 +                                      diagnosis:element_diagnosis[0] });
   1.760 +        }
   1.761 +      }
   1.762 +      return true;
   1.763 +    };
   1.764 +  } else if (pattern === undefined) {
   1.765 +    return function(value) { return true; };
   1.766 +  } else {
   1.767 +    return function (value, diagnosis) {
   1.768 +      if (value !== pattern) {
   1.769 +        return explain(diagnosis, "pattern " + uneval(pattern) + " not === to value " + uneval(value));
   1.770 +      }
   1.771 +      return true;
   1.772 +    };
   1.773 +  }
   1.774 +}
   1.775 +
   1.776 +// Format an explanation for a pattern match failure, as stored in the
   1.777 +// second argument to a matching function.
   1.778 +function format_pattern_match_failure(diagnosis, indent="") {
   1.779 +  var a;
   1.780 +  if (!diagnosis) {
   1.781 +    a = "Matcher did not explain reason for mismatch.";
   1.782 +  } else if (typeof diagnosis == "string") {
   1.783 +    a = diagnosis;
   1.784 +  } else if (diagnosis.property) {
   1.785 +    a = "Property " + uneval(diagnosis.property) + " of object didn't match:\n";
   1.786 +    a += format_pattern_match_failure(diagnosis.diagnosis, indent + "  ");
   1.787 +  }
   1.788 +  return indent + a;
   1.789 +}
   1.790 +
   1.791 +function do_test_pending() {
   1.792 +  ++_tests_pending;
   1.793 +
   1.794 +  _dump("TEST-INFO | (xpcshell/head.js) | test " + _tests_pending +
   1.795 +         " pending\n");
   1.796 +}
   1.797 +
   1.798 +function do_test_finished() {
   1.799 +  _dump("TEST-INFO | (xpcshell/head.js) | test " + _tests_pending +
   1.800 +         " finished\n");
   1.801 +
   1.802 +  if (--_tests_pending == 0)
   1.803 +    _do_quit();
   1.804 +}
   1.805 +
   1.806 +function do_get_file(path, allowNonexistent) {
   1.807 +  try {
   1.808 +    let lf = Components.classes["@mozilla.org/file/directory_service;1"]
   1.809 +      .getService(Components.interfaces.nsIProperties)
   1.810 +      .get("CurWorkD", Components.interfaces.nsILocalFile);
   1.811 +
   1.812 +    let bits = path.split("/");
   1.813 +    for (let i = 0; i < bits.length; i++) {
   1.814 +      if (bits[i]) {
   1.815 +        if (bits[i] == "..")
   1.816 +          lf = lf.parent;
   1.817 +        else
   1.818 +          lf.append(bits[i]);
   1.819 +      }
   1.820 +    }
   1.821 +
   1.822 +    if (!allowNonexistent && !lf.exists()) {
   1.823 +      // Not using do_throw(): caller will continue.
   1.824 +      _passed = false;
   1.825 +      var stack = Components.stack.caller;
   1.826 +      _dump("TEST-UNEXPECTED-FAIL | " + stack.filename + " | [" +
   1.827 +            stack.name + " : " + stack.lineNumber + "] " + lf.path +
   1.828 +            " does not exist\n");
   1.829 +    }
   1.830 +
   1.831 +    return lf;
   1.832 +  }
   1.833 +  catch (ex) {
   1.834 +    do_throw(ex.toString(), Components.stack.caller);
   1.835 +  }
   1.836 +
   1.837 +  return null;
   1.838 +}
   1.839 +
   1.840 +// do_get_cwd() isn't exactly self-explanatory, so provide a helper
   1.841 +function do_get_cwd() {
   1.842 +  return do_get_file("");
   1.843 +}
   1.844 +
   1.845 +function do_load_manifest(path) {
   1.846 +  var lf = do_get_file(path);
   1.847 +  const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
   1.848 +  do_check_true(Components.manager instanceof nsIComponentRegistrar);
   1.849 +  // Previous do_check_true() is not a test check.
   1.850 +  ++_falsePassedChecks;
   1.851 +  Components.manager.autoRegister(lf);
   1.852 +}
   1.853 +
   1.854 +/**
   1.855 + * Parse a DOM document.
   1.856 + *
   1.857 + * @param aPath File path to the document.
   1.858 + * @param aType Content type to use in DOMParser.
   1.859 + *
   1.860 + * @return nsIDOMDocument from the file.
   1.861 + */
   1.862 +function do_parse_document(aPath, aType) {
   1.863 +  switch (aType) {
   1.864 +    case "application/xhtml+xml":
   1.865 +    case "application/xml":
   1.866 +    case "text/xml":
   1.867 +      break;
   1.868 +
   1.869 +    default:
   1.870 +      do_throw("type: expected application/xhtml+xml, application/xml or text/xml," +
   1.871 +                 " got '" + aType + "'",
   1.872 +               Components.stack.caller);
   1.873 +  }
   1.874 +
   1.875 +  var lf = do_get_file(aPath);
   1.876 +  const C_i = Components.interfaces;
   1.877 +  const parserClass = "@mozilla.org/xmlextras/domparser;1";
   1.878 +  const streamClass = "@mozilla.org/network/file-input-stream;1";
   1.879 +  var stream = Components.classes[streamClass]
   1.880 +                         .createInstance(C_i.nsIFileInputStream);
   1.881 +  stream.init(lf, -1, -1, C_i.nsIFileInputStream.CLOSE_ON_EOF);
   1.882 +  var parser = Components.classes[parserClass]
   1.883 +                         .createInstance(C_i.nsIDOMParser);
   1.884 +  var doc = parser.parseFromStream(stream, null, lf.fileSize, aType);
   1.885 +  parser = null;
   1.886 +  stream = null;
   1.887 +  lf = null;
   1.888 +  return doc;
   1.889 +}
   1.890 +
   1.891 +/**
   1.892 + * Registers a function that will run when the test harness is done running all
   1.893 + * tests.
   1.894 + *
   1.895 + * @param aFunction
   1.896 + *        The function to be called when the test harness has finished running.
   1.897 + */
   1.898 +function do_register_cleanup(aFunction)
   1.899 +{
   1.900 +  _cleanupFunctions.push(aFunction);
   1.901 +}
   1.902 +
   1.903 +/**
   1.904 + * Registers a directory with the profile service,
   1.905 + * and return the directory as an nsILocalFile.
   1.906 + *
   1.907 + * @return nsILocalFile of the profile directory.
   1.908 + */
   1.909 +function do_get_profile() {
   1.910 +  if (!runningInParent) {
   1.911 +    _dump("TEST-INFO | (xpcshell/head.js) | Ignoring profile creation from child process.\n");
   1.912 +    return null;
   1.913 +  }
   1.914 +
   1.915 +  if (!_profileInitialized) {
   1.916 +    // Since we have a profile, we will notify profile shutdown topics at
   1.917 +    // the end of the current test, to ensure correct cleanup on shutdown.
   1.918 +    do_register_cleanup(function() {
   1.919 +      let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
   1.920 +                   getService(Components.interfaces.nsIObserverService);
   1.921 +      obsSvc.notifyObservers(null, "profile-change-net-teardown", null);
   1.922 +      obsSvc.notifyObservers(null, "profile-change-teardown", null);
   1.923 +      obsSvc.notifyObservers(null, "profile-before-change", null);
   1.924 +    });
   1.925 +  }
   1.926 +
   1.927 +  let env = Components.classes["@mozilla.org/process/environment;1"]
   1.928 +                      .getService(Components.interfaces.nsIEnvironment);
   1.929 +  // the python harness sets this in the environment for us
   1.930 +  let profd = env.get("XPCSHELL_TEST_PROFILE_DIR");
   1.931 +  let file = Components.classes["@mozilla.org/file/local;1"]
   1.932 +                       .createInstance(Components.interfaces.nsILocalFile);
   1.933 +  file.initWithPath(profd);
   1.934 +
   1.935 +  let dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
   1.936 +                         .getService(Components.interfaces.nsIProperties);
   1.937 +  let provider = {
   1.938 +    getFile: function(prop, persistent) {
   1.939 +      persistent.value = true;
   1.940 +      if (prop == "ProfD" || prop == "ProfLD" || prop == "ProfDS" ||
   1.941 +          prop == "ProfLDS" || prop == "TmpD") {
   1.942 +        return file.clone();
   1.943 +      }
   1.944 +      throw Components.results.NS_ERROR_FAILURE;
   1.945 +    },
   1.946 +    QueryInterface: function(iid) {
   1.947 +      if (iid.equals(Components.interfaces.nsIDirectoryServiceProvider) ||
   1.948 +          iid.equals(Components.interfaces.nsISupports)) {
   1.949 +        return this;
   1.950 +      }
   1.951 +      throw Components.results.NS_ERROR_NO_INTERFACE;
   1.952 +    }
   1.953 +  };
   1.954 +  dirSvc.QueryInterface(Components.interfaces.nsIDirectoryService)
   1.955 +        .registerProvider(provider);
   1.956 +
   1.957 +  let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
   1.958 +        getService(Components.interfaces.nsIObserverService);
   1.959 +
   1.960 +  if (!_profileInitialized) {
   1.961 +    obsSvc.notifyObservers(null, "profile-do-change", "xpcshell-do-get-profile");
   1.962 +    _profileInitialized = true;
   1.963 +  }
   1.964 +
   1.965 +  // The methods of 'provider' will retain this scope so null out everything
   1.966 +  // to avoid spurious leak reports.
   1.967 +  env = null;
   1.968 +  profd = null;
   1.969 +  dirSvc = null;
   1.970 +  provider = null;
   1.971 +  obsSvc = null;
   1.972 +  return file.clone();
   1.973 +}
   1.974 +
   1.975 +/**
   1.976 + * This function loads head.js (this file) in the child process, so that all
   1.977 + * functions defined in this file (do_throw, etc) are available to subsequent
   1.978 + * sendCommand calls.  It also sets various constants used by these functions.
   1.979 + *
   1.980 + * (Note that you may use sendCommand without calling this function first;  you
   1.981 + * simply won't have any of the functions in this file available.)
   1.982 + */
   1.983 +function do_load_child_test_harness()
   1.984 +{
   1.985 +  // Make sure this isn't called from child process
   1.986 +  if (!runningInParent) {
   1.987 +    do_throw("run_test_in_child cannot be called from child!");
   1.988 +  }
   1.989 +
   1.990 +  // Allow to be called multiple times, but only run once
   1.991 +  if (typeof do_load_child_test_harness.alreadyRun != "undefined")
   1.992 +    return;
   1.993 +  do_load_child_test_harness.alreadyRun = 1;
   1.994 +
   1.995 +  _XPCSHELL_PROCESS = "parent";
   1.996 +
   1.997 +  let command =
   1.998 +        "const _HEAD_JS_PATH=" + uneval(_HEAD_JS_PATH) + "; "
   1.999 +      + "const _HTTPD_JS_PATH=" + uneval(_HTTPD_JS_PATH) + "; "
  1.1000 +      + "const _HEAD_FILES=" + uneval(_HEAD_FILES) + "; "
  1.1001 +      + "const _TAIL_FILES=" + uneval(_TAIL_FILES) + "; "
  1.1002 +      + "const _XPCSHELL_PROCESS='child';";
  1.1003 +
  1.1004 +  if (this._TESTING_MODULES_DIR) {
  1.1005 +    command += " const _TESTING_MODULES_DIR=" + uneval(_TESTING_MODULES_DIR) + ";";
  1.1006 +  }
  1.1007 +
  1.1008 +  command += " load(_HEAD_JS_PATH);";
  1.1009 +
  1.1010 +  sendCommand(command);
  1.1011 +}
  1.1012 +
  1.1013 +/**
  1.1014 + * Runs an entire xpcshell unit test in a child process (rather than in chrome,
  1.1015 + * which is the default).
  1.1016 + *
  1.1017 + * This function returns immediately, before the test has completed.
  1.1018 + *
  1.1019 + * @param testFile
  1.1020 + *        The name of the script to run.  Path format same as load().
  1.1021 + * @param optionalCallback.
  1.1022 + *        Optional function to be called (in parent) when test on child is
  1.1023 + *        complete.  If provided, the function must call do_test_finished();
  1.1024 + */
  1.1025 +function run_test_in_child(testFile, optionalCallback)
  1.1026 +{
  1.1027 +  var callback = (typeof optionalCallback == 'undefined') ?
  1.1028 +                    do_test_finished : optionalCallback;
  1.1029 +
  1.1030 +  do_load_child_test_harness();
  1.1031 +
  1.1032 +  var testPath = do_get_file(testFile).path.replace(/\\/g, "/");
  1.1033 +  do_test_pending();
  1.1034 +  sendCommand("_dump('CHILD-TEST-STARTED'); "
  1.1035 +              + "const _TEST_FILE=['" + testPath + "']; _execute_test(); "
  1.1036 +              + "_dump('CHILD-TEST-COMPLETED');",
  1.1037 +              callback);
  1.1038 +}
  1.1039 +
  1.1040 +
  1.1041 +/**
  1.1042 + * Add a test function to the list of tests that are to be run asynchronously.
  1.1043 + *
  1.1044 + * Each test function must call run_next_test() when it's done. Test files
  1.1045 + * should call run_next_test() in their run_test function to execute all
  1.1046 + * async tests.
  1.1047 + *
  1.1048 + * @return the test function that was passed in.
  1.1049 + */
  1.1050 +let _gTests = [];
  1.1051 +function add_test(func) {
  1.1052 +  _gTests.push([false, func]);
  1.1053 +  return func;
  1.1054 +}
  1.1055 +
  1.1056 +// We lazy import Task.jsm so we don't incur a run-time penalty for all tests.
  1.1057 +let _Task;
  1.1058 +
  1.1059 +/**
  1.1060 + * Add a test function which is a Task function.
  1.1061 + *
  1.1062 + * Task functions are functions fed into Task.jsm's Task.spawn(). They are
  1.1063 + * generators that emit promises.
  1.1064 + *
  1.1065 + * If an exception is thrown, a do_check_* comparison fails, or if a rejected
  1.1066 + * promise is yielded, the test function aborts immediately and the test is
  1.1067 + * reported as a failure.
  1.1068 + *
  1.1069 + * Unlike add_test(), there is no need to call run_next_test(). The next test
  1.1070 + * will run automatically as soon the task function is exhausted. To trigger
  1.1071 + * premature (but successful) termination of the function, simply return or
  1.1072 + * throw a Task.Result instance.
  1.1073 + *
  1.1074 + * Example usage:
  1.1075 + *
  1.1076 + * add_task(function test() {
  1.1077 + *   let result = yield Promise.resolve(true);
  1.1078 + *
  1.1079 + *   do_check_true(result);
  1.1080 + *
  1.1081 + *   let secondary = yield someFunctionThatReturnsAPromise(result);
  1.1082 + *   do_check_eq(secondary, "expected value");
  1.1083 + * });
  1.1084 + *
  1.1085 + * add_task(function test_early_return() {
  1.1086 + *   let result = yield somethingThatReturnsAPromise();
  1.1087 + *
  1.1088 + *   if (!result) {
  1.1089 + *     // Test is ended immediately, with success.
  1.1090 + *     return;
  1.1091 + *   }
  1.1092 + *
  1.1093 + *   do_check_eq(result, "foo");
  1.1094 + * });
  1.1095 + */
  1.1096 +function add_task(func) {
  1.1097 +  if (!_Task) {
  1.1098 +    let ns = {};
  1.1099 +    _Task = Components.utils.import("resource://gre/modules/Task.jsm", ns).Task;
  1.1100 +  }
  1.1101 +
  1.1102 +  _gTests.push([true, func]);
  1.1103 +}
  1.1104 +
  1.1105 +/**
  1.1106 + * Runs the next test function from the list of async tests.
  1.1107 + */
  1.1108 +let _gRunningTest = null;
  1.1109 +let _gTestIndex = 0; // The index of the currently running test.
  1.1110 +function run_next_test()
  1.1111 +{
  1.1112 +  function _run_next_test()
  1.1113 +  {
  1.1114 +    if (_gTestIndex < _gTests.length) {
  1.1115 +      do_test_pending();
  1.1116 +      let _isTask;
  1.1117 +      [_isTask, _gRunningTest] = _gTests[_gTestIndex++];
  1.1118 +      _dump("TEST-INFO | " + _TEST_FILE + " | Starting " + _gRunningTest.name);
  1.1119 +
  1.1120 +      if (_isTask) {
  1.1121 +        _Task.spawn(_gRunningTest)
  1.1122 +             .then(run_next_test, do_report_unexpected_exception);
  1.1123 +      } else {
  1.1124 +        // Exceptions do not kill asynchronous tests, so they'll time out.
  1.1125 +        try {
  1.1126 +          _gRunningTest();
  1.1127 +        } catch (e) {
  1.1128 +          do_throw(e);
  1.1129 +        }
  1.1130 +      }
  1.1131 +    }
  1.1132 +  }
  1.1133 +
  1.1134 +  // For sane stacks during failures, we execute this code soon, but not now.
  1.1135 +  // We do this now, before we call do_test_finished(), to ensure the pending
  1.1136 +  // counter (_tests_pending) never reaches 0 while we still have tests to run
  1.1137 +  // (do_execute_soon bumps that counter).
  1.1138 +  do_execute_soon(_run_next_test);
  1.1139 +
  1.1140 +  if (_gRunningTest !== null) {
  1.1141 +    // Close the previous test do_test_pending call.
  1.1142 +    do_test_finished();
  1.1143 +  }
  1.1144 +}
  1.1145 +
  1.1146 +/**
  1.1147 + * End of code adapted from xpcshell head.js
  1.1148 + */
  1.1149 +
  1.1150 +
  1.1151 +/**
  1.1152 + * JavaBridge facilitates communication between Java and JS. See
  1.1153 + * JavascriptBridge.java for the corresponding JavascriptBridge and docs.
  1.1154 + */
  1.1155 +
  1.1156 +function JavaBridge(obj) {
  1.1157 +
  1.1158 +  this._EVENT_TYPE = "Robocop:JS";
  1.1159 +  this._target = obj;
  1.1160 +  // The number of replies needed to answer all outstanding sync calls.
  1.1161 +  this._repliesNeeded = 0;
  1.1162 +  this._Services.obs.addObserver(this, this._EVENT_TYPE, false);
  1.1163 +
  1.1164 +  this._sendMessage("notify-loaded", []);
  1.1165 +};
  1.1166 +
  1.1167 +JavaBridge.prototype = {
  1.1168 +
  1.1169 +  _Services: Components.utils.import(
  1.1170 +    "resource://gre/modules/Services.jsm", {}).Services,
  1.1171 +
  1.1172 +  _sendMessageToJava: Components.utils.import(
  1.1173 +    "resource://gre/modules/Messaging.jsm", {}).sendMessageToJava,
  1.1174 +
  1.1175 +  _sendMessage: function (innerType, args) {
  1.1176 +    this._sendMessageToJava({
  1.1177 +      type: this._EVENT_TYPE,
  1.1178 +      innerType: innerType,
  1.1179 +      method: args[0],
  1.1180 +      args: Array.prototype.slice.call(args, 1),
  1.1181 +    });
  1.1182 +  },
  1.1183 +
  1.1184 +  observe: function(subject, topic, data) {
  1.1185 +    let message = JSON.parse(data);
  1.1186 +    if (message.innerType === "sync-reply") {
  1.1187 +      // Reply to our Javascript-to-Java sync call
  1.1188 +      this._repliesNeeded--;
  1.1189 +      return;
  1.1190 +    }
  1.1191 +    // Call the corresponding method on the target
  1.1192 +    try {
  1.1193 +      this._target[message.method].apply(this._target, message.args);
  1.1194 +    } catch (e) {
  1.1195 +      do_report_unexpected_exception(e, "Failed to call " + message.method);
  1.1196 +    }
  1.1197 +    if (message.innerType === "sync-call") {
  1.1198 +      // Reply for sync message
  1.1199 +      this._sendMessage("sync-reply", [message.method]);
  1.1200 +    }
  1.1201 +  },
  1.1202 +
  1.1203 +  /**
  1.1204 +   * Synchronously call a method in Java,
  1.1205 +   * given the method name followed by a list of arguments.
  1.1206 +   */
  1.1207 +  syncCall: function (methodName /*, ... */) {
  1.1208 +    this._sendMessage("sync-call", arguments);
  1.1209 +    let thread = this._Services.tm.currentThread;
  1.1210 +    let initialReplies = this._repliesNeeded;
  1.1211 +    // Need one more reply to answer the current sync call.
  1.1212 +    this._repliesNeeded++;
  1.1213 +    // Wait for the reply to arrive. Normally we would not want to
  1.1214 +    // spin the event loop, but here we're in a test and our API
  1.1215 +    // specifies a synchronous call, so we spin the loop to wait for
  1.1216 +    // the call to finish.
  1.1217 +    while (this._repliesNeeded > initialReplies) {
  1.1218 +      thread.processNextEvent(true);
  1.1219 +    }
  1.1220 +  },
  1.1221 +
  1.1222 +  /**
  1.1223 +   * Asynchronously call a method in Java,
  1.1224 +   * given the method name followed by a list of arguments.
  1.1225 +   */
  1.1226 +  asyncCall: function (methodName /*, ... */) {
  1.1227 +    this._sendMessage("async-call", arguments);
  1.1228 +  },
  1.1229 +
  1.1230 +  /**
  1.1231 +   * Disconnect with Java.
  1.1232 +   */
  1.1233 +  disconnect: function () {
  1.1234 +    this._Services.obs.removeObserver(this, this._EVENT_TYPE);
  1.1235 +  },
  1.1236 +};

mercurial