mobile/android/base/tests/robocop_head.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim:set ts=2 sw=2 sts=2 et: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 /*
     8  * This file contains common code that is loaded before each test file(s).
     9  * See http://developer.mozilla.org/en/docs/Writing_xpcshell-based_unit_tests
    10  * for more information.
    11  */
    13 const _XPCSHELL_TIMEOUT_MS = 5 * 60 * 1000;
    15 var _quit = false;
    16 var _passed = true;
    17 var _tests_pending = 0;
    18 var _passedChecks = 0, _falsePassedChecks = 0;
    19 var _todoChecks = 0;
    20 var _cleanupFunctions = [];
    21 var _pendingTimers = [];
    22 var _profileInitialized = false;
    24 function _dump(str) {
    25   let start = /^TEST-/.test(str) ? "\n" : "";
    26   if (typeof _XPCSHELL_PROCESS == "undefined") {
    27     dump(start + str);
    28   } else {
    29     dump(start + _XPCSHELL_PROCESS + ": " + str);
    30   }
    31 }
    33 // Disable automatic network detection, so tests work correctly when
    34 // not connected to a network.
    35 let (ios = Components.classes["@mozilla.org/network/io-service;1"]
    36            .getService(Components.interfaces.nsIIOService2)) {
    37   ios.manageOfflineStatus = false;
    38   ios.offline = false;
    39 }
    41 // Determine if we're running on parent or child
    42 let runningInParent = true;
    43 try {
    44   runningInParent = Components.classes["@mozilla.org/xre/runtime;1"].
    45                     getService(Components.interfaces.nsIXULRuntime).processType
    46                     == Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
    47 }
    48 catch (e) { }
    50 try {
    51   if (runningInParent) {
    52     let prefs = Components.classes["@mozilla.org/preferences-service;1"]
    53                 .getService(Components.interfaces.nsIPrefBranch);
    55     // disable necko IPC security checks for xpcshell, as they lack the
    56     // docshells needed to pass them
    57     prefs.setBoolPref("network.disable.ipc.security", true);
    59     // Disable IPv6 lookups for 'localhost' on windows.
    60     if ("@mozilla.org/windows-registry-key;1" in Components.classes) {
    61       prefs.setCharPref("network.dns.ipv4OnlyDomains", "localhost");
    62     }
    63   }
    64 }
    65 catch (e) { }
    67 // Enable crash reporting, if possible
    68 // We rely on the Python harness to set MOZ_CRASHREPORTER_NO_REPORT
    69 // and handle checking for minidumps.
    70 // Note that if we're in a child process, we don't want to init the
    71 // crashreporter component.
    72 try { // nsIXULRuntime is not available in some configurations.
    73   if (runningInParent &&
    74       "@mozilla.org/toolkit/crash-reporter;1" in Components.classes) {
    75     // Remember to update </toolkit/crashreporter/test/unit/test_crashreporter.js>
    76     // too if you change this initial setting.
    77     let (crashReporter =
    78           Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
    79           .getService(Components.interfaces.nsICrashReporter)) {
    80       crashReporter.enabled = true;
    81       crashReporter.minidumpPath = do_get_cwd();
    82     }
    83   }
    84 }
    85 catch (e) { }
    87 /**
    88  * Date.now() is not necessarily monotonically increasing (insert sob story
    89  * about times not being the right tool to use for measuring intervals of time,
    90  * robarnold can tell all), so be wary of error by erring by at least
    91  * _timerFuzz ms.
    92  */
    93 const _timerFuzz = 15;
    95 function _Timer(func, delay) {
    96   delay = Number(delay);
    97   if (delay < 0)
    98     do_throw("do_timeout() delay must be nonnegative");
   100   if (typeof func !== "function")
   101     do_throw("string callbacks no longer accepted; use a function!");
   103   this._func = func;
   104   this._start = Date.now();
   105   this._delay = delay;
   107   var timer = Components.classes["@mozilla.org/timer;1"]
   108                         .createInstance(Components.interfaces.nsITimer);
   109   timer.initWithCallback(this, delay + _timerFuzz, timer.TYPE_ONE_SHOT);
   111   // Keep timer alive until it fires
   112   _pendingTimers.push(timer);
   113 }
   114 _Timer.prototype = {
   115   QueryInterface: function(iid) {
   116     if (iid.equals(Components.interfaces.nsITimerCallback) ||
   117         iid.equals(Components.interfaces.nsISupports))
   118       return this;
   120     throw Components.results.NS_ERROR_NO_INTERFACE;
   121   },
   123   notify: function(timer) {
   124     _pendingTimers.splice(_pendingTimers.indexOf(timer), 1);
   126     // The current nsITimer implementation can undershoot, but even if it
   127     // couldn't, paranoia is probably a virtue here given the potential for
   128     // random orange on tinderboxen.
   129     var end = Date.now();
   130     var elapsed = end - this._start;
   131     if (elapsed >= this._delay) {
   132       try {
   133         this._func.call(null);
   134       } catch (e) {
   135         do_throw("exception thrown from do_timeout callback: " + e);
   136       }
   137       return;
   138     }
   140     // Timer undershot, retry with a little overshoot to try to avoid more
   141     // undershoots.
   142     var newDelay = this._delay - elapsed;
   143     do_timeout(newDelay, this._func);
   144   }
   145 };
   147 function _do_main() {
   148   if (_quit)
   149     return;
   151   _dump("TEST-INFO | (xpcshell/head.js) | running event loop\n");
   153   var thr = Components.classes["@mozilla.org/thread-manager;1"]
   154                       .getService().currentThread;
   156   while (!_quit)
   157     thr.processNextEvent(true);
   159   while (thr.hasPendingEvents())
   160     thr.processNextEvent(true);
   161 }
   163 function _do_quit() {
   164   _dump("TEST-INFO | (xpcshell/head.js) | exiting test\n");
   166   _quit = true;
   167 }
   169 function _dump_exception_stack(stack) {
   170   stack.split("\n").forEach(function(frame) {
   171     if (!frame)
   172       return;
   173     // frame is of the form "fname(args)@file:line"
   174     let frame_regexp = new RegExp("(.*)\\(.*\\)@(.*):(\\d*)", "g");
   175     let parts = frame_regexp.exec(frame);
   176     if (parts)
   177         dump("JS frame :: " + parts[2] + " :: " + (parts[1] ? parts[1] : "anonymous")
   178              + " :: line " + parts[3] + "\n");
   179     else /* Could be a -e (command line string) style location. */
   180         dump("JS frame :: " + frame + "\n");
   181   });
   182 }
   184 /**
   185  * Overrides idleService with a mock.  Idle is commonly used for maintenance
   186  * tasks, thus if a test uses a service that requires the idle service, it will
   187  * start handling them.
   188  * This behaviour would cause random failures and slowdown tests execution,
   189  * for example by running database vacuum or cleanups for each test.
   190  *
   191  * @note Idle service is overridden by default.  If a test requires it, it will
   192  *       have to call do_get_idle() function at least once before use.
   193  */
   194 var _fakeIdleService = {
   195   get registrar() {
   196     delete this.registrar;
   197     return this.registrar =
   198       Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
   199   },
   200   contractID: "@mozilla.org/widget/idleservice;1",
   201   get CID() this.registrar.contractIDToCID(this.contractID),
   203   activate: function FIS_activate()
   204   {
   205     if (!this.originalFactory) {
   206       // Save original factory.
   207       this.originalFactory =
   208         Components.manager.getClassObject(Components.classes[this.contractID],
   209                                           Components.interfaces.nsIFactory);
   210       // Unregister original factory.
   211       this.registrar.unregisterFactory(this.CID, this.originalFactory);
   212       // Replace with the mock.
   213       this.registrar.registerFactory(this.CID, "Fake Idle Service",
   214                                      this.contractID, this.factory
   215       );
   216     }
   217   },
   219   deactivate: function FIS_deactivate()
   220   {
   221     if (this.originalFactory) {
   222       // Unregister the mock.
   223       this.registrar.unregisterFactory(this.CID, this.factory);
   224       // Restore original factory.
   225       this.registrar.registerFactory(this.CID, "Idle Service",
   226                                      this.contractID, this.originalFactory);
   227       delete this.originalFactory;
   228     }
   229   },
   231   factory: {
   232     // nsIFactory
   233     createInstance: function (aOuter, aIID)
   234     {
   235       if (aOuter) {
   236         throw Components.results.NS_ERROR_NO_AGGREGATION;
   237       }
   238       return _fakeIdleService.QueryInterface(aIID);
   239     },
   240     lockFactory: function (aLock) {
   241       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   242     },
   243     QueryInterface: function(aIID) {
   244       if (aIID.equals(Components.interfaces.nsIFactory) ||
   245           aIID.equals(Components.interfaces.nsISupports)) {
   246         return this;
   247       }
   248       throw Components.results.NS_ERROR_NO_INTERFACE;
   249     }
   250   },
   252   // nsIIdleService
   253   get idleTime() 0,
   254   addIdleObserver: function () {},
   255   removeIdleObserver: function () {},
   257   QueryInterface: function(aIID) {
   258     // Useful for testing purposes, see test_get_idle.js.
   259     if (aIID.equals(Components.interfaces.nsIFactory)) {
   260       return this.factory;
   261     }
   262     if (aIID.equals(Components.interfaces.nsIIdleService) ||
   263         aIID.equals(Components.interfaces.nsISupports)) {
   264       return this;
   265     }
   266     throw Components.results.NS_ERROR_NO_INTERFACE;
   267   }
   268 }
   270 /**
   271  * Restores the idle service factory if needed and returns the service's handle.
   272  * @return A handle to the idle service.
   273  */
   274 function do_get_idle() {
   275   _fakeIdleService.deactivate();
   276   return Components.classes[_fakeIdleService.contractID]
   277                    .getService(Components.interfaces.nsIIdleService);
   278 }
   280 // Map resource://test/ to current working directory and
   281 // resource://testing-common/ to the shared test modules directory.
   282 function _register_protocol_handlers() {
   283   let (ios = Components.classes["@mozilla.org/network/io-service;1"]
   284              .getService(Components.interfaces.nsIIOService)) {
   285     let protocolHandler =
   286       ios.getProtocolHandler("resource")
   287          .QueryInterface(Components.interfaces.nsIResProtocolHandler);
   288     let curDirURI = ios.newFileURI(do_get_cwd());
   289     protocolHandler.setSubstitution("test", curDirURI);
   291     if (this._TESTING_MODULES_DIR) {
   292       let modulesFile = Components.classes["@mozilla.org/file/local;1"].
   293                         createInstance(Components.interfaces.nsILocalFile);
   294       modulesFile.initWithPath(_TESTING_MODULES_DIR);
   296       if (!modulesFile.exists()) {
   297         throw new Error("Specified modules directory does not exist: " +
   298                         _TESTING_MODULES_DIR);
   299       }
   301       if (!modulesFile.isDirectory()) {
   302         throw new Error("Specified modules directory is not a directory: " +
   303                         _TESTING_MODULES_DIR);
   304       }
   306       let modulesURI = ios.newFileURI(modulesFile);
   308       protocolHandler.setSubstitution("testing-common", modulesURI);
   309     }
   310   }
   311 }
   313 function _execute_test() {
   314   _register_protocol_handlers();
   316   // Override idle service by default.
   317   // Call do_get_idle() to restore the factory and get the service.
   318   _fakeIdleService.activate();
   320   // Terminate asynchronous tests when a global timeout occurs.
   321   do_timeout(_XPCSHELL_TIMEOUT_MS, function _do_main_timeout() {
   322     try {
   323       do_throw("test timed out");
   324     } catch (e if e == Components.results.NS_ERROR_ABORT) {
   325       // We don't want do_timeout to report the do_throw exception again.
   326     }
   327   });
   329   // _HEAD_FILES is dynamically defined by <runxpcshelltests.py>.
   330   _load_files(_HEAD_FILES);
   331   // _TEST_FILE is dynamically defined by <runxpcshelltests.py>.
   332   _load_files(_TEST_FILE);
   334   try {
   335     do_test_pending();
   336     run_test();
   337     do_test_finished();
   338     _do_main();
   339   } catch (e) {
   340     _passed = false;
   341     // do_check failures are already logged and set _quit to true and throw
   342     // NS_ERROR_ABORT. If both of those are true it is likely this exception
   343     // has already been logged so there is no need to log it again. It's
   344     // possible that this will mask an NS_ERROR_ABORT that happens after a
   345     // do_check failure though.
   346     if (!_quit || e != Components.results.NS_ERROR_ABORT) {
   347       msg = "TEST-UNEXPECTED-FAIL | ";
   348       if (e.fileName) {
   349         msg += e.fileName;
   350         if (e.lineNumber) {
   351           msg += ":" + e.lineNumber;
   352         }
   353       } else {
   354         msg += "xpcshell/head.js";
   355       }
   356       msg += " | " + e;
   357       if (e.stack) {
   358         _dump(msg + " - See following stack:\n");
   359         _dump_exception_stack(e.stack);
   360       }
   361       else {
   362         _dump(msg + "\n");
   363       }
   364     }
   365   }
   367   // _TAIL_FILES is dynamically defined by <runxpcshelltests.py>.
   368   _load_files(_TAIL_FILES);
   370   // Execute all of our cleanup functions.
   371   var func;
   372   while ((func = _cleanupFunctions.pop()))
   373     func();
   375   // Restore idle service to avoid leaks.
   376   _fakeIdleService.deactivate();
   378   if (!_passed)
   379     return;
   381   var truePassedChecks = _passedChecks - _falsePassedChecks;
   382   if (truePassedChecks > 0) {
   383     _dump("TEST-PASS | (xpcshell/head.js) | " + truePassedChecks + " (+ " +
   384             _falsePassedChecks + ") check(s) passed\n");
   385     _dump("TEST-INFO | (xpcshell/head.js) | " + _todoChecks +
   386             " check(s) todo\n");
   387   } else {
   388     // ToDo: switch to TEST-UNEXPECTED-FAIL when all tests have been updated. (Bug 496443)
   389     _dump("TEST-INFO | (xpcshell/head.js) | No (+ " + _falsePassedChecks + ") checks actually run\n");
   390   }
   391 }
   393 /**
   394  * Loads files.
   395  *
   396  * @param aFiles Array of files to load.
   397  */
   398 function _load_files(aFiles) {
   399   function loadTailFile(element, index, array) {
   400     load(element);
   401   }
   403   aFiles.forEach(loadTailFile);
   404 }
   407 /************** Functions to be used from the tests **************/
   409 /**
   410  * Prints a message to the output log.
   411  */
   412 function do_print(msg) {
   413   var caller_stack = Components.stack.caller;
   414   _dump("TEST-INFO | " + caller_stack.filename + " | " + msg + "\n");
   415 }
   417 /**
   418  * Calls the given function at least the specified number of milliseconds later.
   419  * The callback will not undershoot the given time, but it might overshoot --
   420  * don't expect precision!
   421  *
   422  * @param delay : uint
   423  *   the number of milliseconds to delay
   424  * @param callback : function() : void
   425  *   the function to call
   426  */
   427 function do_timeout(delay, func) {
   428   new _Timer(func, Number(delay));
   429 }
   431 function do_execute_soon(callback) {
   432   do_test_pending();
   433   var tm = Components.classes["@mozilla.org/thread-manager;1"]
   434                      .getService(Components.interfaces.nsIThreadManager);
   436   tm.mainThread.dispatch({
   437     run: function() {
   438       try {
   439         callback();
   440       } catch (e) {
   441         // do_check failures are already logged and set _quit to true and throw
   442         // NS_ERROR_ABORT. If both of those are true it is likely this exception
   443         // has already been logged so there is no need to log it again. It's
   444         // possible that this will mask an NS_ERROR_ABORT that happens after a
   445         // do_check failure though.
   446         if (!_quit || e != Components.results.NS_ERROR_ABORT) {
   447           _dump("TEST-UNEXPECTED-FAIL | (xpcshell/head.js) | " + e);
   448           if (e.stack) {
   449             dump(" - See following stack:\n");
   450             _dump_exception_stack(e.stack);
   451           }
   452           else {
   453             dump("\n");
   454           }
   455           _do_quit();
   456         }
   457       }
   458       finally {
   459         do_test_finished();
   460       }
   461     }
   462   }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
   463 }
   465 function do_throw(text, stack) {
   466   if (!stack)
   467     stack = Components.stack.caller;
   469   _passed = false;
   470   _dump("TEST-UNEXPECTED-FAIL | " + stack.filename + " | " + text +
   471         " - See following stack:\n");
   472   var frame = Components.stack;
   473   while (frame != null) {
   474     _dump(frame + "\n");
   475     frame = frame.caller;
   476   }
   478   _do_quit();
   479   throw Components.results.NS_ERROR_ABORT;
   480 }
   482 function do_throw_todo(text, stack) {
   483   if (!stack)
   484     stack = Components.stack.caller;
   486   _passed = false;
   487   _dump("TEST-UNEXPECTED-PASS | " + stack.filename + " | " + text +
   488         " - See following stack:\n");
   489   var frame = Components.stack;
   490   while (frame != null) {
   491     _dump(frame + "\n");
   492     frame = frame.caller;
   493   }
   495   _do_quit();
   496   throw Components.results.NS_ERROR_ABORT;
   497 }
   499 function do_report_unexpected_exception(ex, text) {
   500   var caller_stack = Components.stack.caller;
   501   text = text ? text + " - " : "";
   503   _passed = false;
   504   _dump("TEST-UNEXPECTED-FAIL | " + caller_stack.filename + " | " + text +
   505         "Unexpected exception " + ex + ", see following stack:\n" + ex.stack +
   506         "\n");
   508   _do_quit();
   509   throw Components.results.NS_ERROR_ABORT;
   510 }
   512 function do_note_exception(ex, text) {
   513   var caller_stack = Components.stack.caller;
   514   text = text ? text + " - " : "";
   516   _dump("TEST-INFO | " + caller_stack.filename + " | " + text +
   517         "Swallowed exception " + ex + ", see following stack:\n" + ex.stack +
   518         "\n");
   519 }
   521 function _do_check_neq(left, right, stack, todo) {
   522   if (!stack)
   523     stack = Components.stack.caller;
   525   var text = left + " != " + right;
   526   if (left == right) {
   527     if (!todo) {
   528       do_throw(text, stack);
   529     } else {
   530       ++_todoChecks;
   531       _dump("TEST-KNOWN-FAIL | " + stack.filename + " | [" + stack.name +
   532             " : " + stack.lineNumber + "] " + text +"\n");
   533     }
   534   } else {
   535     if (!todo) {
   536       ++_passedChecks;
   537       _dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " +
   538             stack.lineNumber + "] " + text + "\n");
   539     } else {
   540       do_throw_todo(text, stack);
   541     }
   542   }
   543 }
   545 function do_check_neq(left, right, stack) {
   546   if (!stack)
   547     stack = Components.stack.caller;
   549   _do_check_neq(left, right, stack, false);
   550 }
   552 function todo_check_neq(left, right, stack) {
   553   if (!stack)
   554       stack = Components.stack.caller;
   556   _do_check_neq(left, right, stack, true);
   557 }
   559 function do_report_result(passed, text, stack, todo) {
   560   if (passed) {
   561     if (todo) {
   562       do_throw_todo(text, stack);
   563     } else {
   564       ++_passedChecks;
   565       _dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " +
   566             stack.lineNumber + "] " + text + "\n");
   567     }
   568   } else {
   569     if (todo) {
   570       ++_todoChecks;
   571       _dump("TEST-KNOWN-FAIL | " + stack.filename + " | [" + stack.name +
   572             " : " + stack.lineNumber + "] " + text +"\n");
   573     } else {
   574       do_throw(text, stack);
   575     }
   576   }
   577 }
   579 function _do_check_eq(left, right, stack, todo) {
   580   if (!stack)
   581     stack = Components.stack.caller;
   583   var text = left + " == " + right;
   584   do_report_result(left == right, text, stack, todo);
   585 }
   587 function do_check_eq(left, right, stack) {
   588   if (!stack)
   589     stack = Components.stack.caller;
   591   _do_check_eq(left, right, stack, false);
   592 }
   594 function todo_check_eq(left, right, stack) {
   595   if (!stack)
   596       stack = Components.stack.caller;
   598   _do_check_eq(left, right, stack, true);
   599 }
   601 function do_check_true(condition, stack) {
   602   if (!stack)
   603     stack = Components.stack.caller;
   605   do_check_eq(condition, true, stack);
   606 }
   608 function todo_check_true(condition, stack) {
   609   if (!stack)
   610     stack = Components.stack.caller;
   612   todo_check_eq(condition, true, stack);
   613 }
   615 function do_check_false(condition, stack) {
   616   if (!stack)
   617     stack = Components.stack.caller;
   619   do_check_eq(condition, false, stack);
   620 }
   622 function todo_check_false(condition, stack) {
   623   if (!stack)
   624     stack = Components.stack.caller;
   626   todo_check_eq(condition, false, stack);
   627 }
   629 function do_check_null(condition, stack=Components.stack.caller) {
   630   do_check_eq(condition, null, stack);
   631 }
   633 function todo_check_null(condition, stack=Components.stack.caller) {
   634   todo_check_eq(condition, null, stack);
   635 }
   637 /**
   638  * Check that |value| matches |pattern|.
   639  *
   640  * A |value| matches a pattern |pattern| if any one of the following is true:
   641  *
   642  * - |value| and |pattern| are both objects; |pattern|'s enumerable
   643  *   properties' values are valid patterns; and for each enumerable
   644  *   property |p| of |pattern|, plus 'length' if present at all, |value|
   645  *   has a property |p| whose value matches |pattern.p|. Note that if |j|
   646  *   has other properties not present in |p|, |j| may still match |p|.
   647  *
   648  * - |value| and |pattern| are equal string, numeric, or boolean literals
   649  *
   650  * - |pattern| is |undefined| (this is a wildcard pattern)
   651  *
   652  * - typeof |pattern| == "function", and |pattern(value)| is true.
   653  *
   654  * For example:
   655  *
   656  * do_check_matches({x:1}, {x:1})       // pass
   657  * do_check_matches({x:1}, {})          // fail: all pattern props required
   658  * do_check_matches({x:1}, {x:2})       // fail: values must match
   659  * do_check_matches({x:1}, {x:1, y:2})  // pass: extra props tolerated
   660  *
   661  * // Property order is irrelevant.
   662  * do_check_matches({x:"foo", y:"bar"}, {y:"bar", x:"foo"}) // pass
   663  *
   664  * do_check_matches({x:undefined}, {x:1}) // pass: 'undefined' is wildcard
   665  * do_check_matches({x:undefined}, {x:2})
   666  * do_check_matches({x:undefined}, {y:2}) // fail: 'x' must still be there
   667  *
   668  * // Patterns nest.
   669  * do_check_matches({a:1, b:{c:2,d:undefined}}, {a:1, b:{c:2,d:3}})
   670  *
   671  * // 'length' property counts, even if non-enumerable.
   672  * do_check_matches([3,4,5], [3,4,5])     // pass
   673  * do_check_matches([3,4,5], [3,5,5])     // fail; value doesn't match
   674  * do_check_matches([3,4,5], [3,4,5,6])   // fail; length doesn't match
   675  *
   676  * // functions in patterns get applied.
   677  * do_check_matches({foo:function (v) v.length == 2}, {foo:"hi"}) // pass
   678  * do_check_matches({foo:function (v) v.length == 2}, {bar:"hi"}) // fail
   679  * do_check_matches({foo:function (v) v.length == 2}, {foo:"hello"}) // fail
   680  *
   681  * // We don't check constructors, prototypes, or classes. However, if
   682  * // pattern has a 'length' property, we require values to match that as
   683  * // well, even if 'length' is non-enumerable in the pattern. So arrays
   684  * // are useful as patterns.
   685  * do_check_matches({0:0, 1:1, length:2}, [0,1])  // pass
   686  * do_check_matches({0:1}, [1,2])                 // pass
   687  * do_check_matches([0], {0:0, length:1})         // pass
   688  *
   689  * Notes:
   690  *
   691  * The 'length' hack gives us reasonably intuitive handling of arrays.
   692  *
   693  * This is not a tight pattern-matcher; it's only good for checking data
   694  * from well-behaved sources. For example:
   695  * - By default, we don't mind values having extra properties.
   696  * - We don't check for proxies or getters.
   697  * - We don't check the prototype chain.
   698  * However, if you know the values are, say, JSON, which is pretty
   699  * well-behaved, and if you want to tolerate additional properties
   700  * appearing on the JSON for backward-compatibility, then do_check_matches
   701  * is ideal. If you do want to be more careful, you can use function
   702  * patterns to implement more stringent checks.
   703  */
   704 function do_check_matches(pattern, value, stack=Components.stack.caller, todo=false) {
   705   var matcher = pattern_matcher(pattern);
   706   var text = "VALUE: " + uneval(value) + "\nPATTERN: " + uneval(pattern) + "\n";
   707   var diagnosis = []
   708   if (matcher(value, diagnosis)) {
   709     do_report_result(true, "value matches pattern:\n" + text, stack, todo);
   710   } else {
   711     text = ("value doesn't match pattern:\n" +
   712             text +
   713             "DIAGNOSIS: " +
   714             format_pattern_match_failure(diagnosis[0]) + "\n");
   715     do_report_result(false, text, stack, todo);
   716   }
   717 }
   719 function todo_check_matches(pattern, value, stack=Components.stack.caller) {
   720   do_check_matches(pattern, value, stack, true);
   721 }
   723 // Return a pattern-matching function of one argument, |value|, that
   724 // returns true if |value| matches |pattern|.
   725 //
   726 // If the pattern doesn't match, and the pattern-matching function was
   727 // passed its optional |diagnosis| argument, the pattern-matching function
   728 // sets |diagnosis|'s '0' property to a JSON-ish description of the portion
   729 // of the pattern that didn't match, which can be formatted legibly by
   730 // format_pattern_match_failure.
   731 function pattern_matcher(pattern) {
   732   function explain(diagnosis, reason) {
   733     if (diagnosis) {
   734       diagnosis[0] = reason;
   735     }
   736     return false;
   737   }
   738   if (typeof pattern == "function") {
   739     return pattern;
   740   } else if (typeof pattern == "object" && pattern) {
   741     var matchers = [[p, pattern_matcher(pattern[p])] for (p in pattern)];
   742     // Kludge: include 'length', if not enumerable. (If it is enumerable,
   743     // we picked it up in the array comprehension, above.
   744     ld = Object.getOwnPropertyDescriptor(pattern, 'length');
   745     if (ld && !ld.enumerable) {
   746       matchers.push(['length', pattern_matcher(pattern.length)])
   747     }
   748     return function (value, diagnosis) {
   749       if (!(value && typeof value == "object")) {
   750         return explain(diagnosis, "value not object");
   751       }
   752       for (let [p, m] of matchers) {
   753         var element_diagnosis = [];
   754         if (!(p in value && m(value[p], element_diagnosis))) {
   755           return explain(diagnosis, { property:p,
   756                                       diagnosis:element_diagnosis[0] });
   757         }
   758       }
   759       return true;
   760     };
   761   } else if (pattern === undefined) {
   762     return function(value) { return true; };
   763   } else {
   764     return function (value, diagnosis) {
   765       if (value !== pattern) {
   766         return explain(diagnosis, "pattern " + uneval(pattern) + " not === to value " + uneval(value));
   767       }
   768       return true;
   769     };
   770   }
   771 }
   773 // Format an explanation for a pattern match failure, as stored in the
   774 // second argument to a matching function.
   775 function format_pattern_match_failure(diagnosis, indent="") {
   776   var a;
   777   if (!diagnosis) {
   778     a = "Matcher did not explain reason for mismatch.";
   779   } else if (typeof diagnosis == "string") {
   780     a = diagnosis;
   781   } else if (diagnosis.property) {
   782     a = "Property " + uneval(diagnosis.property) + " of object didn't match:\n";
   783     a += format_pattern_match_failure(diagnosis.diagnosis, indent + "  ");
   784   }
   785   return indent + a;
   786 }
   788 function do_test_pending() {
   789   ++_tests_pending;
   791   _dump("TEST-INFO | (xpcshell/head.js) | test " + _tests_pending +
   792          " pending\n");
   793 }
   795 function do_test_finished() {
   796   _dump("TEST-INFO | (xpcshell/head.js) | test " + _tests_pending +
   797          " finished\n");
   799   if (--_tests_pending == 0)
   800     _do_quit();
   801 }
   803 function do_get_file(path, allowNonexistent) {
   804   try {
   805     let lf = Components.classes["@mozilla.org/file/directory_service;1"]
   806       .getService(Components.interfaces.nsIProperties)
   807       .get("CurWorkD", Components.interfaces.nsILocalFile);
   809     let bits = path.split("/");
   810     for (let i = 0; i < bits.length; i++) {
   811       if (bits[i]) {
   812         if (bits[i] == "..")
   813           lf = lf.parent;
   814         else
   815           lf.append(bits[i]);
   816       }
   817     }
   819     if (!allowNonexistent && !lf.exists()) {
   820       // Not using do_throw(): caller will continue.
   821       _passed = false;
   822       var stack = Components.stack.caller;
   823       _dump("TEST-UNEXPECTED-FAIL | " + stack.filename + " | [" +
   824             stack.name + " : " + stack.lineNumber + "] " + lf.path +
   825             " does not exist\n");
   826     }
   828     return lf;
   829   }
   830   catch (ex) {
   831     do_throw(ex.toString(), Components.stack.caller);
   832   }
   834   return null;
   835 }
   837 // do_get_cwd() isn't exactly self-explanatory, so provide a helper
   838 function do_get_cwd() {
   839   return do_get_file("");
   840 }
   842 function do_load_manifest(path) {
   843   var lf = do_get_file(path);
   844   const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
   845   do_check_true(Components.manager instanceof nsIComponentRegistrar);
   846   // Previous do_check_true() is not a test check.
   847   ++_falsePassedChecks;
   848   Components.manager.autoRegister(lf);
   849 }
   851 /**
   852  * Parse a DOM document.
   853  *
   854  * @param aPath File path to the document.
   855  * @param aType Content type to use in DOMParser.
   856  *
   857  * @return nsIDOMDocument from the file.
   858  */
   859 function do_parse_document(aPath, aType) {
   860   switch (aType) {
   861     case "application/xhtml+xml":
   862     case "application/xml":
   863     case "text/xml":
   864       break;
   866     default:
   867       do_throw("type: expected application/xhtml+xml, application/xml or text/xml," +
   868                  " got '" + aType + "'",
   869                Components.stack.caller);
   870   }
   872   var lf = do_get_file(aPath);
   873   const C_i = Components.interfaces;
   874   const parserClass = "@mozilla.org/xmlextras/domparser;1";
   875   const streamClass = "@mozilla.org/network/file-input-stream;1";
   876   var stream = Components.classes[streamClass]
   877                          .createInstance(C_i.nsIFileInputStream);
   878   stream.init(lf, -1, -1, C_i.nsIFileInputStream.CLOSE_ON_EOF);
   879   var parser = Components.classes[parserClass]
   880                          .createInstance(C_i.nsIDOMParser);
   881   var doc = parser.parseFromStream(stream, null, lf.fileSize, aType);
   882   parser = null;
   883   stream = null;
   884   lf = null;
   885   return doc;
   886 }
   888 /**
   889  * Registers a function that will run when the test harness is done running all
   890  * tests.
   891  *
   892  * @param aFunction
   893  *        The function to be called when the test harness has finished running.
   894  */
   895 function do_register_cleanup(aFunction)
   896 {
   897   _cleanupFunctions.push(aFunction);
   898 }
   900 /**
   901  * Registers a directory with the profile service,
   902  * and return the directory as an nsILocalFile.
   903  *
   904  * @return nsILocalFile of the profile directory.
   905  */
   906 function do_get_profile() {
   907   if (!runningInParent) {
   908     _dump("TEST-INFO | (xpcshell/head.js) | Ignoring profile creation from child process.\n");
   909     return null;
   910   }
   912   if (!_profileInitialized) {
   913     // Since we have a profile, we will notify profile shutdown topics at
   914     // the end of the current test, to ensure correct cleanup on shutdown.
   915     do_register_cleanup(function() {
   916       let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
   917                    getService(Components.interfaces.nsIObserverService);
   918       obsSvc.notifyObservers(null, "profile-change-net-teardown", null);
   919       obsSvc.notifyObservers(null, "profile-change-teardown", null);
   920       obsSvc.notifyObservers(null, "profile-before-change", null);
   921     });
   922   }
   924   let env = Components.classes["@mozilla.org/process/environment;1"]
   925                       .getService(Components.interfaces.nsIEnvironment);
   926   // the python harness sets this in the environment for us
   927   let profd = env.get("XPCSHELL_TEST_PROFILE_DIR");
   928   let file = Components.classes["@mozilla.org/file/local;1"]
   929                        .createInstance(Components.interfaces.nsILocalFile);
   930   file.initWithPath(profd);
   932   let dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
   933                          .getService(Components.interfaces.nsIProperties);
   934   let provider = {
   935     getFile: function(prop, persistent) {
   936       persistent.value = true;
   937       if (prop == "ProfD" || prop == "ProfLD" || prop == "ProfDS" ||
   938           prop == "ProfLDS" || prop == "TmpD") {
   939         return file.clone();
   940       }
   941       throw Components.results.NS_ERROR_FAILURE;
   942     },
   943     QueryInterface: function(iid) {
   944       if (iid.equals(Components.interfaces.nsIDirectoryServiceProvider) ||
   945           iid.equals(Components.interfaces.nsISupports)) {
   946         return this;
   947       }
   948       throw Components.results.NS_ERROR_NO_INTERFACE;
   949     }
   950   };
   951   dirSvc.QueryInterface(Components.interfaces.nsIDirectoryService)
   952         .registerProvider(provider);
   954   let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
   955         getService(Components.interfaces.nsIObserverService);
   957   if (!_profileInitialized) {
   958     obsSvc.notifyObservers(null, "profile-do-change", "xpcshell-do-get-profile");
   959     _profileInitialized = true;
   960   }
   962   // The methods of 'provider' will retain this scope so null out everything
   963   // to avoid spurious leak reports.
   964   env = null;
   965   profd = null;
   966   dirSvc = null;
   967   provider = null;
   968   obsSvc = null;
   969   return file.clone();
   970 }
   972 /**
   973  * This function loads head.js (this file) in the child process, so that all
   974  * functions defined in this file (do_throw, etc) are available to subsequent
   975  * sendCommand calls.  It also sets various constants used by these functions.
   976  *
   977  * (Note that you may use sendCommand without calling this function first;  you
   978  * simply won't have any of the functions in this file available.)
   979  */
   980 function do_load_child_test_harness()
   981 {
   982   // Make sure this isn't called from child process
   983   if (!runningInParent) {
   984     do_throw("run_test_in_child cannot be called from child!");
   985   }
   987   // Allow to be called multiple times, but only run once
   988   if (typeof do_load_child_test_harness.alreadyRun != "undefined")
   989     return;
   990   do_load_child_test_harness.alreadyRun = 1;
   992   _XPCSHELL_PROCESS = "parent";
   994   let command =
   995         "const _HEAD_JS_PATH=" + uneval(_HEAD_JS_PATH) + "; "
   996       + "const _HTTPD_JS_PATH=" + uneval(_HTTPD_JS_PATH) + "; "
   997       + "const _HEAD_FILES=" + uneval(_HEAD_FILES) + "; "
   998       + "const _TAIL_FILES=" + uneval(_TAIL_FILES) + "; "
   999       + "const _XPCSHELL_PROCESS='child';";
  1001   if (this._TESTING_MODULES_DIR) {
  1002     command += " const _TESTING_MODULES_DIR=" + uneval(_TESTING_MODULES_DIR) + ";";
  1005   command += " load(_HEAD_JS_PATH);";
  1007   sendCommand(command);
  1010 /**
  1011  * Runs an entire xpcshell unit test in a child process (rather than in chrome,
  1012  * which is the default).
  1014  * This function returns immediately, before the test has completed.
  1016  * @param testFile
  1017  *        The name of the script to run.  Path format same as load().
  1018  * @param optionalCallback.
  1019  *        Optional function to be called (in parent) when test on child is
  1020  *        complete.  If provided, the function must call do_test_finished();
  1021  */
  1022 function run_test_in_child(testFile, optionalCallback)
  1024   var callback = (typeof optionalCallback == 'undefined') ?
  1025                     do_test_finished : optionalCallback;
  1027   do_load_child_test_harness();
  1029   var testPath = do_get_file(testFile).path.replace(/\\/g, "/");
  1030   do_test_pending();
  1031   sendCommand("_dump('CHILD-TEST-STARTED'); "
  1032               + "const _TEST_FILE=['" + testPath + "']; _execute_test(); "
  1033               + "_dump('CHILD-TEST-COMPLETED');",
  1034               callback);
  1038 /**
  1039  * Add a test function to the list of tests that are to be run asynchronously.
  1041  * Each test function must call run_next_test() when it's done. Test files
  1042  * should call run_next_test() in their run_test function to execute all
  1043  * async tests.
  1045  * @return the test function that was passed in.
  1046  */
  1047 let _gTests = [];
  1048 function add_test(func) {
  1049   _gTests.push([false, func]);
  1050   return func;
  1053 // We lazy import Task.jsm so we don't incur a run-time penalty for all tests.
  1054 let _Task;
  1056 /**
  1057  * Add a test function which is a Task function.
  1059  * Task functions are functions fed into Task.jsm's Task.spawn(). They are
  1060  * generators that emit promises.
  1062  * If an exception is thrown, a do_check_* comparison fails, or if a rejected
  1063  * promise is yielded, the test function aborts immediately and the test is
  1064  * reported as a failure.
  1066  * Unlike add_test(), there is no need to call run_next_test(). The next test
  1067  * will run automatically as soon the task function is exhausted. To trigger
  1068  * premature (but successful) termination of the function, simply return or
  1069  * throw a Task.Result instance.
  1071  * Example usage:
  1073  * add_task(function test() {
  1074  *   let result = yield Promise.resolve(true);
  1076  *   do_check_true(result);
  1078  *   let secondary = yield someFunctionThatReturnsAPromise(result);
  1079  *   do_check_eq(secondary, "expected value");
  1080  * });
  1082  * add_task(function test_early_return() {
  1083  *   let result = yield somethingThatReturnsAPromise();
  1085  *   if (!result) {
  1086  *     // Test is ended immediately, with success.
  1087  *     return;
  1088  *   }
  1090  *   do_check_eq(result, "foo");
  1091  * });
  1092  */
  1093 function add_task(func) {
  1094   if (!_Task) {
  1095     let ns = {};
  1096     _Task = Components.utils.import("resource://gre/modules/Task.jsm", ns).Task;
  1099   _gTests.push([true, func]);
  1102 /**
  1103  * Runs the next test function from the list of async tests.
  1104  */
  1105 let _gRunningTest = null;
  1106 let _gTestIndex = 0; // The index of the currently running test.
  1107 function run_next_test()
  1109   function _run_next_test()
  1111     if (_gTestIndex < _gTests.length) {
  1112       do_test_pending();
  1113       let _isTask;
  1114       [_isTask, _gRunningTest] = _gTests[_gTestIndex++];
  1115       _dump("TEST-INFO | " + _TEST_FILE + " | Starting " + _gRunningTest.name);
  1117       if (_isTask) {
  1118         _Task.spawn(_gRunningTest)
  1119              .then(run_next_test, do_report_unexpected_exception);
  1120       } else {
  1121         // Exceptions do not kill asynchronous tests, so they'll time out.
  1122         try {
  1123           _gRunningTest();
  1124         } catch (e) {
  1125           do_throw(e);
  1131   // For sane stacks during failures, we execute this code soon, but not now.
  1132   // We do this now, before we call do_test_finished(), to ensure the pending
  1133   // counter (_tests_pending) never reaches 0 while we still have tests to run
  1134   // (do_execute_soon bumps that counter).
  1135   do_execute_soon(_run_next_test);
  1137   if (_gRunningTest !== null) {
  1138     // Close the previous test do_test_pending call.
  1139     do_test_finished();
  1143 /**
  1144  * End of code adapted from xpcshell head.js
  1145  */
  1148 /**
  1149  * JavaBridge facilitates communication between Java and JS. See
  1150  * JavascriptBridge.java for the corresponding JavascriptBridge and docs.
  1151  */
  1153 function JavaBridge(obj) {
  1155   this._EVENT_TYPE = "Robocop:JS";
  1156   this._target = obj;
  1157   // The number of replies needed to answer all outstanding sync calls.
  1158   this._repliesNeeded = 0;
  1159   this._Services.obs.addObserver(this, this._EVENT_TYPE, false);
  1161   this._sendMessage("notify-loaded", []);
  1162 };
  1164 JavaBridge.prototype = {
  1166   _Services: Components.utils.import(
  1167     "resource://gre/modules/Services.jsm", {}).Services,
  1169   _sendMessageToJava: Components.utils.import(
  1170     "resource://gre/modules/Messaging.jsm", {}).sendMessageToJava,
  1172   _sendMessage: function (innerType, args) {
  1173     this._sendMessageToJava({
  1174       type: this._EVENT_TYPE,
  1175       innerType: innerType,
  1176       method: args[0],
  1177       args: Array.prototype.slice.call(args, 1),
  1178     });
  1179   },
  1181   observe: function(subject, topic, data) {
  1182     let message = JSON.parse(data);
  1183     if (message.innerType === "sync-reply") {
  1184       // Reply to our Javascript-to-Java sync call
  1185       this._repliesNeeded--;
  1186       return;
  1188     // Call the corresponding method on the target
  1189     try {
  1190       this._target[message.method].apply(this._target, message.args);
  1191     } catch (e) {
  1192       do_report_unexpected_exception(e, "Failed to call " + message.method);
  1194     if (message.innerType === "sync-call") {
  1195       // Reply for sync message
  1196       this._sendMessage("sync-reply", [message.method]);
  1198   },
  1200   /**
  1201    * Synchronously call a method in Java,
  1202    * given the method name followed by a list of arguments.
  1203    */
  1204   syncCall: function (methodName /*, ... */) {
  1205     this._sendMessage("sync-call", arguments);
  1206     let thread = this._Services.tm.currentThread;
  1207     let initialReplies = this._repliesNeeded;
  1208     // Need one more reply to answer the current sync call.
  1209     this._repliesNeeded++;
  1210     // Wait for the reply to arrive. Normally we would not want to
  1211     // spin the event loop, but here we're in a test and our API
  1212     // specifies a synchronous call, so we spin the loop to wait for
  1213     // the call to finish.
  1214     while (this._repliesNeeded > initialReplies) {
  1215       thread.processNextEvent(true);
  1217   },
  1219   /**
  1220    * Asynchronously call a method in Java,
  1221    * given the method name followed by a list of arguments.
  1222    */
  1223   asyncCall: function (methodName /*, ... */) {
  1224     this._sendMessage("async-call", arguments);
  1225   },
  1227   /**
  1228    * Disconnect with Java.
  1229    */
  1230   disconnect: function () {
  1231     this._Services.obs.removeObserver(this, this._EVENT_TYPE);
  1232   },
  1233 };

mercurial