Bug 1014482: make Assert.jsm methods globally available and deprecate XPCShell-test's custom assert methods. This changes `do_check_matches()` semantics significantly. r=gps
authorMike de Boer <mdeboer@mozilla.com>
Fri, 30 May 2014 16:26:42 +0200
changeset 185928 47db238357541e6fa17ac12c53d7d9605659322b
parent 185927 609ceda14f75e5bf2f7a761080702904a19de2b2
child 185929 f3899da8641010232efd72a9fa3b2a8b5288046c
push id44197
push userryanvm@gmail.com
push dateFri, 30 May 2014 20:24:52 +0000
treeherdermozilla-inbound@c57a20e0c95c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1014482
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1014482: make Assert.jsm methods globally available and deprecate XPCShell-test's custom assert methods. This changes `do_check_matches()` semantics significantly. r=gps
testing/modules/Assert.jsm
testing/xpcshell/head.js
--- a/testing/modules/Assert.jsm
+++ b/testing/modules/Assert.jsm
@@ -182,17 +182,17 @@ proto.report = function(failed, actual, 
     operator: operator
   });
   if (!this._reporter) {
     // If no custom reporter is set, throw the error.
     if (failed) {
       throw err;
     }
   } else {
-    this._reporter(failed ? err : null, message, err.stack);
+    this._reporter(failed ? err : null, err.message, err.stack);
   }
 };
 
 /**
  * 4. Pure assertion tests whether a value is truthy, as determined by !!guard.
  * assert.ok(guard, message_opt);
  * This statement is equivalent to assert.equal(true, !!guard, message_opt);.
  * To test strictly for the value true, use assert.strictEqual(true, guard,
--- a/testing/xpcshell/head.js
+++ b/testing/xpcshell/head.js
@@ -14,18 +14,33 @@ var _quit = false;
 var _passed = true;
 var _tests_pending = 0;
 var _passedChecks = 0, _falsePassedChecks = 0;
 var _todoChecks = 0;
 var _cleanupFunctions = [];
 var _pendingTimers = [];
 var _profileInitialized = false;
 
+// Register the testing-common resource protocol early, to have access to its
+// modules.
+_register_modules_protocol_handler();
+
 let _Promise = Components.utils.import("resource://gre/modules/Promise.jsm", this).Promise;
 
+// Support a common assertion library, Assert.jsm.
+let AssertCls = Components.utils.import("resource://testing-common/Assert.jsm", null).Assert;
+// Pass a custom report function for xpcshell-test style reporting.
+let Assert = new AssertCls(function(err, message, stack) {
+  if (err) {
+    do_report_result(false, err.message, err.stack);
+  } else {
+    do_report_result(true, message, stack);
+  }
+});
+
 let _log = function (action, params) {
   if (typeof _XPCSHELL_PROCESS != "undefined") {
     params.process = _XPCSHELL_PROCESS;
   }
   params.action = action;
   params._time = Date.now();
   dump("\n" + JSON.stringify(params) + "\n");
 }
@@ -308,44 +323,58 @@ function do_get_idle() {
   _fakeIdleService.deactivate();
   return Components.classes[_fakeIdleService.contractID]
                    .getService(Components.interfaces.nsIIdleService);
 }
 
 // Map resource://test/ to current working directory and
 // resource://testing-common/ to the shared test modules directory.
 function _register_protocol_handlers() {
-  let (ios = Components.classes["@mozilla.org/network/io-service;1"]
-             .getService(Components.interfaces.nsIIOService)) {
-    let protocolHandler =
-      ios.getProtocolHandler("resource")
-         .QueryInterface(Components.interfaces.nsIResProtocolHandler);
-    let curDirURI = ios.newFileURI(do_get_cwd());
-    protocolHandler.setSubstitution("test", curDirURI);
+  let ios = Components.classes["@mozilla.org/network/io-service;1"]
+                      .getService(Components.interfaces.nsIIOService);
+  let protocolHandler =
+    ios.getProtocolHandler("resource")
+       .QueryInterface(Components.interfaces.nsIResProtocolHandler);
+
+  let curDirURI = ios.newFileURI(do_get_cwd());
+  protocolHandler.setSubstitution("test", curDirURI);
 
-    if (this._TESTING_MODULES_DIR) {
-      let modulesFile = Components.classes["@mozilla.org/file/local;1"].
-                        createInstance(Components.interfaces.nsILocalFile);
-      modulesFile.initWithPath(_TESTING_MODULES_DIR);
+  _register_modules_protocol_handler();
+}
+
+function _register_modules_protocol_handler() {
+  if (!this._TESTING_MODULES_DIR) {
+    throw new Error("Please define a path where the testing modules can be " +
+                    "found in a variable called '_TESTING_MODULES_DIR' before " +
+                    "head.js is included.");
+  }
 
-      if (!modulesFile.exists()) {
-        throw new Error("Specified modules directory does not exist: " +
-                        _TESTING_MODULES_DIR);
-      }
+  let ios = Components.classes["@mozilla.org/network/io-service;1"]
+                      .getService(Components.interfaces.nsIIOService);
+  let protocolHandler =
+    ios.getProtocolHandler("resource")
+       .QueryInterface(Components.interfaces.nsIResProtocolHandler);
+
+  let modulesFile = Components.classes["@mozilla.org/file/local;1"].
+                    createInstance(Components.interfaces.nsILocalFile);
+  modulesFile.initWithPath(_TESTING_MODULES_DIR);
 
-      if (!modulesFile.isDirectory()) {
-        throw new Error("Specified modules directory is not a directory: " +
-                        _TESTING_MODULES_DIR);
-      }
+  if (!modulesFile.exists()) {
+    throw new Error("Specified modules directory does not exist: " +
+                    _TESTING_MODULES_DIR);
+  }
 
-      let modulesURI = ios.newFileURI(modulesFile);
+  if (!modulesFile.isDirectory()) {
+    throw new Error("Specified modules directory is not a directory: " +
+                    _TESTING_MODULES_DIR);
+  }
 
-      protocolHandler.setSubstitution("testing-common", modulesURI);
-    }
-  }
+  let modulesURI = ios.newFileURI(modulesFile);
+
+  protocolHandler.setSubstitution("testing-common", modulesURI);
 }
 
 function _execute_test() {
   _register_protocol_handlers();
 
   // Override idle service by default.
   // Call do_get_idle() to restore the factory and get the service.
   _fakeIdleService.activate();
@@ -358,33 +387,21 @@ function _execute_test() {
                             text, stack, fileName);
   });
 
   // _HEAD_FILES is dynamically defined by <runxpcshelltests.py>.
   _load_files(_HEAD_FILES);
   // _TEST_FILE is dynamically defined by <runxpcshelltests.py>.
   _load_files(_TEST_FILE);
 
-  // Support a common assertion library, Assert.jsm.
-  let Assert = Components.utils.import("resource://testing-common/Assert.jsm", null).Assert;
-  // Pass a custom report function for xpcshell-test style reporting.
-  let assertImpl = new Assert(function(err, message, stack) {
-    if (err) {
-      do_report_result(false, err.message, err.stack);
-    } else {
-      do_report_result(true, message, stack);
-    }
-  });
-  // Allow Assert.jsm methods to be tacked to the current scope.
-  this.export_assertions = function() {
-    for (let func in assertImpl) {
-      this[func] = assertImpl[func].bind(assertImpl);
-    }
-  };
-  this.Assert = assertImpl;
+  // Tack Assert.jsm methods to the current scope.
+  this.Assert = Assert;
+  for (let func in Assert) {
+    this[func] = Assert[func].bind(Assert);
+  }
 
   try {
     do_test_pending("MAIN run_test");
     run_test();
     do_test_finished("MAIN run_test");
     _do_main();
   } catch (e) {
     _passed = false;
@@ -682,22 +699,17 @@ function do_note_exception(ex, text) {
   var caller_stack = Components.stack.caller;
   text = text ? text + " - " : "";
 
   _log_message_with_stack("test_info", ex, ex.stack,
                           caller_stack.filename, text + "Swallowed exception ");
 }
 
 function _do_check_neq(left, right, stack, todo) {
-  if (!stack)
-    stack = Components.stack.caller;
-
-  var text = _wrap_with_quotes_if_necessary(left) + " != " +
-             _wrap_with_quotes_if_necessary(right);
-  do_report_result(left != right, text, stack, todo);
+  Assert.notEqual(left, right);
 }
 
 function do_check_neq(left, right, stack) {
   if (!stack)
     stack = Components.stack.caller;
 
   _do_check_neq(left, right, stack, false);
 }
@@ -742,214 +754,57 @@ function _do_check_eq(left, right, stack
     stack = Components.stack.caller;
 
   var text = _wrap_with_quotes_if_necessary(left) + " == " +
              _wrap_with_quotes_if_necessary(right);
   do_report_result(left == right, text, stack, todo);
 }
 
 function do_check_eq(left, right, stack) {
-  if (!stack)
-    stack = Components.stack.caller;
-
-  _do_check_eq(left, right, stack, false);
+  Assert.equal(left, right);
 }
 
 function todo_check_eq(left, right, stack) {
   if (!stack)
       stack = Components.stack.caller;
 
   _do_check_eq(left, right, stack, true);
 }
 
 function do_check_true(condition, stack) {
-  if (!stack)
-    stack = Components.stack.caller;
-
-  do_check_eq(condition, true, stack);
+  Assert.ok(condition);
 }
 
 function todo_check_true(condition, stack) {
   if (!stack)
     stack = Components.stack.caller;
 
   todo_check_eq(condition, true, stack);
 }
 
 function do_check_false(condition, stack) {
-  if (!stack)
-    stack = Components.stack.caller;
-
-  do_check_eq(condition, false, stack);
+  Assert.ok(!condition, stack);
 }
 
 function todo_check_false(condition, stack) {
   if (!stack)
     stack = Components.stack.caller;
 
   todo_check_eq(condition, false, stack);
 }
 
-function do_check_null(condition, stack=Components.stack.caller) {
-  do_check_eq(condition, null, stack);
+function do_check_null(condition, stack) {
+  Assert.equal(condition, null);
 }
 
 function todo_check_null(condition, stack=Components.stack.caller) {
   todo_check_eq(condition, null, stack);
 }
-
-/**
- * Check that |value| matches |pattern|.
- *
- * A |value| matches a pattern |pattern| if any one of the following is true:
- *
- * - |value| and |pattern| are both objects; |pattern|'s enumerable
- *   properties' values are valid patterns; and for each enumerable
- *   property |p| of |pattern|, plus 'length' if present at all, |value|
- *   has a property |p| whose value matches |pattern.p|. Note that if |j|
- *   has other properties not present in |p|, |j| may still match |p|.
- *
- * - |value| and |pattern| are equal string, numeric, or boolean literals
- *
- * - |pattern| is |undefined| (this is a wildcard pattern)
- *
- * - typeof |pattern| == "function", and |pattern(value)| is true.
- *
- * For example:
- *
- * do_check_matches({x:1}, {x:1})       // pass
- * do_check_matches({x:1}, {})          // fail: all pattern props required
- * do_check_matches({x:1}, {x:2})       // fail: values must match
- * do_check_matches({x:1}, {x:1, y:2})  // pass: extra props tolerated
- *
- * // Property order is irrelevant.
- * do_check_matches({x:"foo", y:"bar"}, {y:"bar", x:"foo"}) // pass
- *
- * do_check_matches({x:undefined}, {x:1}) // pass: 'undefined' is wildcard
- * do_check_matches({x:undefined}, {x:2})
- * do_check_matches({x:undefined}, {y:2}) // fail: 'x' must still be there
- *
- * // Patterns nest.
- * do_check_matches({a:1, b:{c:2,d:undefined}}, {a:1, b:{c:2,d:3}})
- *
- * // 'length' property counts, even if non-enumerable.
- * do_check_matches([3,4,5], [3,4,5])     // pass
- * do_check_matches([3,4,5], [3,5,5])     // fail; value doesn't match
- * do_check_matches([3,4,5], [3,4,5,6])   // fail; length doesn't match
- *
- * // functions in patterns get applied.
- * do_check_matches({foo:function (v) v.length == 2}, {foo:"hi"}) // pass
- * do_check_matches({foo:function (v) v.length == 2}, {bar:"hi"}) // fail
- * do_check_matches({foo:function (v) v.length == 2}, {foo:"hello"}) // fail
- *
- * // We don't check constructors, prototypes, or classes. However, if
- * // pattern has a 'length' property, we require values to match that as
- * // well, even if 'length' is non-enumerable in the pattern. So arrays
- * // are useful as patterns.
- * do_check_matches({0:0, 1:1, length:2}, [0,1])  // pass
- * do_check_matches({0:1}, [1,2])                 // pass
- * do_check_matches([0], {0:0, length:1})         // pass
- *
- * Notes:
- *
- * The 'length' hack gives us reasonably intuitive handling of arrays.
- *
- * This is not a tight pattern-matcher; it's only good for checking data
- * from well-behaved sources. For example:
- * - By default, we don't mind values having extra properties.
- * - We don't check for proxies or getters.
- * - We don't check the prototype chain.
- * However, if you know the values are, say, JSON, which is pretty
- * well-behaved, and if you want to tolerate additional properties
- * appearing on the JSON for backward-compatibility, then do_check_matches
- * is ideal. If you do want to be more careful, you can use function
- * patterns to implement more stringent checks.
- */
-function do_check_matches(pattern, value, stack=Components.stack.caller, todo=false) {
-  var matcher = pattern_matcher(pattern);
-  var text = "VALUE: " + uneval(value) + "\nPATTERN: " + uneval(pattern) + "\n";
-  var diagnosis = []
-  if (matcher(value, diagnosis)) {
-    do_report_result(true, "value matches pattern:\n" + text, stack, todo);
-  } else {
-    text = ("value doesn't match pattern:\n" +
-            text +
-            "DIAGNOSIS: " +
-            format_pattern_match_failure(diagnosis[0]) + "\n");
-    do_report_result(false, text, stack, todo);
-  }
-}
-
-function todo_check_matches(pattern, value, stack=Components.stack.caller) {
-  do_check_matches(pattern, value, stack, true);
-}
-
-// Return a pattern-matching function of one argument, |value|, that
-// returns true if |value| matches |pattern|.
-//
-// If the pattern doesn't match, and the pattern-matching function was
-// passed its optional |diagnosis| argument, the pattern-matching function
-// sets |diagnosis|'s '0' property to a JSON-ish description of the portion
-// of the pattern that didn't match, which can be formatted legibly by
-// format_pattern_match_failure.
-function pattern_matcher(pattern) {
-  function explain(diagnosis, reason) {
-    if (diagnosis) {
-      diagnosis[0] = reason;
-    }
-    return false;
-  }
-  if (typeof pattern == "function") {
-    return pattern;
-  } else if (typeof pattern == "object" && pattern) {
-    var matchers = [[p, pattern_matcher(pattern[p])] for (p in pattern)];
-    // Kludge: include 'length', if not enumerable. (If it is enumerable,
-    // we picked it up in the array comprehension, above.
-    var ld = Object.getOwnPropertyDescriptor(pattern, 'length');
-    if (ld && !ld.enumerable) {
-      matchers.push(['length', pattern_matcher(pattern.length)])
-    }
-    return function (value, diagnosis) {
-      if (!(value && typeof value == "object")) {
-        return explain(diagnosis, "value not object");
-      }
-      for (let [p, m] of matchers) {
-        var element_diagnosis = [];
-        if (!(p in value && m(value[p], element_diagnosis))) {
-          return explain(diagnosis, { property:p,
-                                      diagnosis:element_diagnosis[0] });
-        }
-      }
-      return true;
-    };
-  } else if (pattern === undefined) {
-    return function(value) { return true; };
-  } else {
-    return function (value, diagnosis) {
-      if (value !== pattern) {
-        return explain(diagnosis, "pattern " + uneval(pattern) + " not === to value " + uneval(value));
-      }
-      return true;
-    };
-  }
-}
-
-// Format an explanation for a pattern match failure, as stored in the
-// second argument to a matching function.
-function format_pattern_match_failure(diagnosis, indent="") {
-  var a;
-  if (!diagnosis) {
-    a = "Matcher did not explain reason for mismatch.";
-  } else if (typeof diagnosis == "string") {
-    a = diagnosis;
-  } else if (diagnosis.property) {
-    a = "Property " + uneval(diagnosis.property) + " of object didn't match:\n";
-    a += format_pattern_match_failure(diagnosis.diagnosis, indent + "  ");
-  }
-  return indent + a;
+function do_check_matches(pattern, value) {
+  Assert.deepEqual(pattern, value);
 }
 
 // Check that |func| throws an nsIException that has
 // |Components.results[resultName]| as the value of its 'result' property.
 function do_check_throws_nsIException(func, resultName,
                                       stack=Components.stack.caller, todo=false)
 {
   let expected = Components.results[resultName];