testing/modules/Assert.jsm

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 // http://wiki.commonjs.org/wiki/Unit_Testing/1.0
     6 // When you see a javadoc comment that contains a number, it's a reference to a
     7 // specific section of the CommonJS spec.
     8 //
     9 // Originally from narwhal.js (http://narwhaljs.org)
    10 // Copyright (c) 2009 Thomas Robinson <280north.com>
    11 // MIT license: http://opensource.org/licenses/MIT
    13 "use strict";
    15 this.EXPORTED_SYMBOLS = [
    16   "Assert"
    17 ];
    19 /**
    20  * 1. The assert module provides functions that throw AssertionError's when
    21  * particular conditions are not met.
    22  *
    23  * To use the module you'll need to instantiate it first, which allows consumers
    24  * to override certain behavior on the newly obtained instance. For examples,
    25  * see the javadoc comments for the `report` member function.
    26  */
    27 let Assert = this.Assert = function(reporterFunc) {
    28   if (reporterFunc)
    29     this.setReporter(reporterFunc);
    30 };
    32 function instanceOf(object, type) {
    33   return Object.prototype.toString.call(object) == "[object " + type + "]";
    34 }
    36 function replacer(key, value) {
    37   if (value === undefined) {
    38     return "" + value;
    39   }
    40   if (typeof value === "number" && (isNaN(value) || !isFinite(value))) {
    41     return value.toString();
    42   }
    43   if (typeof value === "function" || instanceOf(value, "RegExp")) {
    44     return value.toString();
    45   }
    46   return value;
    47 }
    49 const kTruncateLength = 128;
    51 function truncate(text, newLength = kTruncateLength) {
    52   if (typeof text == "string") {
    53     return text.length < newLength ? text : text.slice(0, newLength);
    54   } else {
    55     return text;
    56   }
    57 }
    59 function getMessage(error, prefix = "") {
    60   let actual, expected;
    61   // Wrap calls to JSON.stringify in try...catch blocks, as they may throw. If
    62   // so, fall back to toString().
    63   try {
    64     actual = JSON.stringify(error.actual, replacer);
    65   } catch (ex) {
    66     actual = Object.prototype.toString.call(error.actual);
    67   }
    68   try {
    69     expected = JSON.stringify(error.expected, replacer);
    70   } catch (ex) {
    71     expected = Object.prototype.toString.call(error.expected);
    72   }
    73   let message = prefix;
    74   if (error.operator) {
    75     message += (prefix ? " - " : "") + truncate(actual) + " " + error.operator +
    76                " " + truncate(expected);
    77   }
    78   return message;
    79 }
    81 /**
    82  * 2. The AssertionError is defined in assert.
    83  *
    84  * Example:
    85  * new assert.AssertionError({
    86  *   message: message,
    87  *   actual: actual,
    88  *   expected: expected,
    89  *   operator: operator
    90  * });
    91  *
    92  * At present only the four keys mentioned above are used and
    93  * understood by the spec. Implementations or sub modules can pass
    94  * other keys to the AssertionError's constructor - they will be
    95  * ignored.
    96  */
    97 Assert.AssertionError = function(options) {
    98   this.name = "AssertionError";
    99   this.actual = options.actual;
   100   this.expected = options.expected;
   101   this.operator = options.operator;
   102   this.message = getMessage(this, options.message);
   103   // The part of the stack that comes from this module is not interesting.
   104   let stack = Components.stack;
   105   do {
   106     stack = stack.caller;
   107   } while(stack.filename && stack.filename.contains("Assert.jsm"))
   108   this.stack = stack;
   109 };
   111 // assert.AssertionError instanceof Error
   112 Assert.AssertionError.prototype = Object.create(Error.prototype, {
   113   constructor: {
   114     value: Assert.AssertionError,
   115     enumerable: false,
   116     writable: true,
   117     configurable: true
   118   }
   119 });
   121 let proto = Assert.prototype;
   123 proto._reporter = null;
   124 /**
   125  * Set a custom assertion report handler function. Arguments passed in to this
   126  * function are:
   127  * err (AssertionError|null) An error object when the assertion failed or null
   128  *                           when it passed
   129  * message (string) Message describing the assertion
   130  * stack (stack) Stack trace of the assertion function
   131  *
   132  * Example:
   133  * ```js
   134  * Assert.setReporter(function customReporter(err, message, stack) {
   135  *   if (err) {
   136  *     do_report_result(false, err.message, err.stack);
   137  *   } else {
   138  *     do_report_result(true, message, stack);
   139  *   }
   140  * });
   141  * ```
   142  *
   143  * @param reporterFunc
   144  *        (function) Report handler function
   145  */
   146 proto.setReporter = function(reporterFunc) {
   147   this._reporter = reporterFunc;
   148 };
   150 /**
   151  * 3. All of the following functions must throw an AssertionError when a
   152  * corresponding condition is not met, with a message that may be undefined if
   153  * not provided.  All assertion methods provide both the actual and expected
   154  * values to the assertion error for display purposes.
   155  *
   156  * This report method only throws errors on assertion failures, as per spec,
   157  * but consumers of this module (think: xpcshell-test, mochitest) may want to
   158  * override this default implementation.
   159  *
   160  * Example:
   161  * ```js
   162  * // The following will report an assertion failure.
   163  * this.report(1 != 2, 1, 2, "testing JS number math!", "==");
   164  * ```
   165  *
   166  * @param failed
   167  *        (boolean) Indicates if the assertion failed or not
   168  * @param actual
   169  *        (mixed) The result of evaluating the assertion
   170  * @param expected (optional)
   171  *        (mixed) Expected result from the test author
   172  * @param message (optional)
   173  *        (string) Short explanation of the expected result
   174  * @param operator (optional)
   175  *        (string) Operation qualifier used by the assertion method (ex: '==')
   176  */
   177 proto.report = function(failed, actual, expected, message, operator) {
   178   let err = new Assert.AssertionError({
   179     message: message,
   180     actual: actual,
   181     expected: expected,
   182     operator: operator
   183   });
   184   if (!this._reporter) {
   185     // If no custom reporter is set, throw the error.
   186     if (failed) {
   187       throw err;
   188     }
   189   } else {
   190     this._reporter(failed ? err : null, message, err.stack);
   191   }
   192 };
   194 /**
   195  * 4. Pure assertion tests whether a value is truthy, as determined by !!guard.
   196  * assert.ok(guard, message_opt);
   197  * This statement is equivalent to assert.equal(true, !!guard, message_opt);.
   198  * To test strictly for the value true, use assert.strictEqual(true, guard,
   199  * message_opt);.
   200  *
   201  * @param value
   202  *        (mixed) Test subject to be evaluated as truthy
   203  * @param message (optional)
   204  *        (string) Short explanation of the expected result
   205  */
   206 proto.ok = function(value, message) {
   207   this.report(!value, value, true, message, "==");
   208 };
   210 /**
   211  * 5. The equality assertion tests shallow, coercive equality with ==.
   212  * assert.equal(actual, expected, message_opt);
   213  *
   214  * @param actual
   215  *        (mixed) Test subject to be evaluated as equivalent to `expected`
   216  * @param expected
   217  *        (mixed) Test reference to evaluate against `actual`
   218  * @param message (optional)
   219  *        (string) Short explanation of the expected result
   220  */
   221 proto.equal = function equal(actual, expected, message) {
   222   this.report(actual != expected, actual, expected, message, "==");
   223 };
   225 /**
   226  * 6. The non-equality assertion tests for whether two objects are not equal
   227  * with != assert.notEqual(actual, expected, message_opt);
   228  *
   229  * @param actual
   230  *        (mixed) Test subject to be evaluated as NOT equivalent to `expected`
   231  * @param expected
   232  *        (mixed) Test reference to evaluate against `actual`
   233  * @param message (optional)
   234  *        (string) Short explanation of the expected result
   235  */
   236 proto.notEqual = function notEqual(actual, expected, message) {
   237   this.report(actual == expected, actual, expected, message, "!=");
   238 };
   240 /**
   241  * 7. The equivalence assertion tests a deep equality relation.
   242  * assert.deepEqual(actual, expected, message_opt);
   243  *
   244  * We check using the most exact approximation of equality between two objects
   245  * to keep the chance of false positives to a minimum.
   246  * `JSON.stringify` is not designed to be used for this purpose; objects may
   247  * have ambiguous `toJSON()` implementations that would influence the test.
   248  *
   249  * @param actual
   250  *        (mixed) Test subject to be evaluated as equivalent to `expected`, including nested properties
   251  * @param expected
   252  *        (mixed) Test reference to evaluate against `actual`
   253  * @param message (optional)
   254  *        (string) Short explanation of the expected result
   255  */
   256 proto.deepEqual = function deepEqual(actual, expected, message) {
   257   this.report(!_deepEqual(actual, expected), actual, expected, message, "deepEqual");
   258 };
   260 function _deepEqual(actual, expected) {
   261   // 7.1. All identical values are equivalent, as determined by ===.
   262   if (actual === expected) {
   263     return true;
   264   // 7.2. If the expected value is a Date object, the actual value is
   265   // equivalent if it is also a Date object that refers to the same time.
   266   } else if (instanceOf(actual, "Date") && instanceOf(expected, "Date")) {
   267     return actual.getTime() === expected.getTime();
   268   // 7.3 If the expected value is a RegExp object, the actual value is
   269   // equivalent if it is also a RegExp object with the same source and
   270   // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
   271   } else if (instanceOf(actual, "RegExp") && instanceOf(expected, "RegExp")) {
   272     return actual.source === expected.source &&
   273            actual.global === expected.global &&
   274            actual.multiline === expected.multiline &&
   275            actual.lastIndex === expected.lastIndex &&
   276            actual.ignoreCase === expected.ignoreCase;
   277   // 7.4. Other pairs that do not both pass typeof value == "object",
   278   // equivalence is determined by ==.
   279   } else if (typeof actual != "object" && typeof expected != "object") {
   280     return actual == expected;
   281   // 7.5 For all other Object pairs, including Array objects, equivalence is
   282   // determined by having the same number of owned properties (as verified
   283   // with Object.prototype.hasOwnProperty.call), the same set of keys
   284   // (although not necessarily the same order), equivalent values for every
   285   // corresponding key, and an identical 'prototype' property. Note: this
   286   // accounts for both named and indexed properties on Arrays.
   287   } else {
   288     return objEquiv(actual, expected);
   289   }
   290 }
   292 function isUndefinedOrNull(value) {
   293   return value === null || value === undefined;
   294 }
   296 function isArguments(object) {
   297   return instanceOf(object, "Arguments");
   298 }
   300 function objEquiv(a, b) {
   301   if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
   302     return false;
   303   }
   304   // An identical 'prototype' property.
   305   if (a.prototype !== b.prototype) {
   306     return false;
   307   }
   308   // Object.keys may be broken through screwy arguments passing. Converting to
   309   // an array solves the problem.
   310   if (isArguments(a)) {
   311     if (!isArguments(b)) {
   312       return false;
   313     }
   314     a = pSlice.call(a);
   315     b = pSlice.call(b);
   316     return _deepEqual(a, b);
   317   }
   318   let ka, kb, key, i;
   319   try {
   320     ka = Object.keys(a);
   321     kb = Object.keys(b);
   322   } catch (e) {
   323     // Happens when one is a string literal and the other isn't
   324     return false;
   325   }
   326   // Having the same number of owned properties (keys incorporates
   327   // hasOwnProperty)
   328   if (ka.length != kb.length)
   329     return false;
   330   // The same set of keys (although not necessarily the same order),
   331   ka.sort();
   332   kb.sort();
   333   // Equivalent values for every corresponding key, and possibly expensive deep 
   334   // test
   335   for (i = ka.length - 1; i >= 0; i--) {
   336     key = ka[i];
   337     if (!_deepEqual(a[key], b[key])) {
   338       return false;
   339     }
   340   }
   341   return true;
   342 }
   344 /**
   345  * 8. The non-equivalence assertion tests for any deep inequality.
   346  * assert.notDeepEqual(actual, expected, message_opt);
   347  *
   348  * @param actual
   349  *        (mixed) Test subject to be evaluated as NOT equivalent to `expected`, including nested properties
   350  * @param expected
   351  *        (mixed) Test reference to evaluate against `actual`
   352  * @param message (optional)
   353  *        (string) Short explanation of the expected result
   354  */
   355 proto.notDeepEqual = function notDeepEqual(actual, expected, message) {
   356   this.report(_deepEqual(actual, expected), actual, expected, message, "notDeepEqual");
   357 };
   359 /**
   360  * 9. The strict equality assertion tests strict equality, as determined by ===.
   361  * assert.strictEqual(actual, expected, message_opt);
   362  *
   363  * @param actual
   364  *        (mixed) Test subject to be evaluated as strictly equivalent to `expected`
   365  * @param expected
   366  *        (mixed) Test reference to evaluate against `actual`
   367  * @param message (optional)
   368  *        (string) Short explanation of the expected result
   369  */
   370 proto.strictEqual = function strictEqual(actual, expected, message) {
   371   this.report(actual !== expected, actual, expected, message, "===");
   372 };
   374 /**
   375  * 10. The strict non-equality assertion tests for strict inequality, as
   376  * determined by !==.  assert.notStrictEqual(actual, expected, message_opt);
   377  *
   378  * @param actual
   379  *        (mixed) Test subject to be evaluated as NOT strictly equivalent to `expected`
   380  * @param expected
   381  *        (mixed) Test reference to evaluate against `actual`
   382  * @param message (optional)
   383  *        (string) Short explanation of the expected result
   384  */
   385 proto.notStrictEqual = function notStrictEqual(actual, expected, message) {
   386   this.report(actual === expected, actual, expected, message, "!==");
   387 };
   389 function expectedException(actual, expected) {
   390   if (!actual || !expected) {
   391     return false;
   392   }
   394   if (instanceOf(expected, "RegExp")) {
   395     return expected.test(actual);
   396   } else if (actual instanceof expected) {
   397     return true;
   398   } else if (expected.call({}, actual) === true) {
   399     return true;
   400   }
   402   return false;
   403 }
   405 /**
   406  * 11. Expected to throw an error:
   407  * assert.throws(block, Error_opt, message_opt);
   408  *
   409  * @param block
   410  *        (function) Function block to evaluate and catch eventual thrown errors
   411  * @param expected (optional)
   412  *        (mixed) Test reference to evaluate against the thrown result from `block`
   413  * @param message (optional)
   414  *        (string) Short explanation of the expected result
   415  */
   416 proto.throws = function(block, expected, message) {
   417   let actual;
   419   if (typeof expected === "string") {
   420     message = expected;
   421     expected = null;
   422   }
   424   try {
   425     block();
   426   } catch (e) {
   427     actual = e;
   428   }
   430   message = (expected && expected.name ? " (" + expected.name + ")." : ".") +
   431             (message ? " " + message : ".");
   433   if (!actual) {
   434     this.report(true, actual, expected, "Missing expected exception" + message);
   435   }
   437   if ((actual && expected && !expectedException(actual, expected))) {
   438     throw actual;
   439   }
   441   this.report(false, expected, expected, message);
   442 };

mercurial