testing/modules/Assert.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/modules/Assert.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,442 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
     1.9 +// When you see a javadoc comment that contains a number, it's a reference to a
    1.10 +// specific section of the CommonJS spec.
    1.11 +//
    1.12 +// Originally from narwhal.js (http://narwhaljs.org)
    1.13 +// Copyright (c) 2009 Thomas Robinson <280north.com>
    1.14 +// MIT license: http://opensource.org/licenses/MIT
    1.15 +
    1.16 +"use strict";
    1.17 +
    1.18 +this.EXPORTED_SYMBOLS = [
    1.19 +  "Assert"
    1.20 +];
    1.21 +
    1.22 +/**
    1.23 + * 1. The assert module provides functions that throw AssertionError's when
    1.24 + * particular conditions are not met.
    1.25 + *
    1.26 + * To use the module you'll need to instantiate it first, which allows consumers
    1.27 + * to override certain behavior on the newly obtained instance. For examples,
    1.28 + * see the javadoc comments for the `report` member function.
    1.29 + */
    1.30 +let Assert = this.Assert = function(reporterFunc) {
    1.31 +  if (reporterFunc)
    1.32 +    this.setReporter(reporterFunc);
    1.33 +};
    1.34 +
    1.35 +function instanceOf(object, type) {
    1.36 +  return Object.prototype.toString.call(object) == "[object " + type + "]";
    1.37 +}
    1.38 +
    1.39 +function replacer(key, value) {
    1.40 +  if (value === undefined) {
    1.41 +    return "" + value;
    1.42 +  }
    1.43 +  if (typeof value === "number" && (isNaN(value) || !isFinite(value))) {
    1.44 +    return value.toString();
    1.45 +  }
    1.46 +  if (typeof value === "function" || instanceOf(value, "RegExp")) {
    1.47 +    return value.toString();
    1.48 +  }
    1.49 +  return value;
    1.50 +}
    1.51 +
    1.52 +const kTruncateLength = 128;
    1.53 +
    1.54 +function truncate(text, newLength = kTruncateLength) {
    1.55 +  if (typeof text == "string") {
    1.56 +    return text.length < newLength ? text : text.slice(0, newLength);
    1.57 +  } else {
    1.58 +    return text;
    1.59 +  }
    1.60 +}
    1.61 +
    1.62 +function getMessage(error, prefix = "") {
    1.63 +  let actual, expected;
    1.64 +  // Wrap calls to JSON.stringify in try...catch blocks, as they may throw. If
    1.65 +  // so, fall back to toString().
    1.66 +  try {
    1.67 +    actual = JSON.stringify(error.actual, replacer);
    1.68 +  } catch (ex) {
    1.69 +    actual = Object.prototype.toString.call(error.actual);
    1.70 +  }
    1.71 +  try {
    1.72 +    expected = JSON.stringify(error.expected, replacer);
    1.73 +  } catch (ex) {
    1.74 +    expected = Object.prototype.toString.call(error.expected);
    1.75 +  }
    1.76 +  let message = prefix;
    1.77 +  if (error.operator) {
    1.78 +    message += (prefix ? " - " : "") + truncate(actual) + " " + error.operator +
    1.79 +               " " + truncate(expected);
    1.80 +  }
    1.81 +  return message;
    1.82 +}
    1.83 +
    1.84 +/**
    1.85 + * 2. The AssertionError is defined in assert.
    1.86 + *
    1.87 + * Example:
    1.88 + * new assert.AssertionError({
    1.89 + *   message: message,
    1.90 + *   actual: actual,
    1.91 + *   expected: expected,
    1.92 + *   operator: operator
    1.93 + * });
    1.94 + *
    1.95 + * At present only the four keys mentioned above are used and
    1.96 + * understood by the spec. Implementations or sub modules can pass
    1.97 + * other keys to the AssertionError's constructor - they will be
    1.98 + * ignored.
    1.99 + */
   1.100 +Assert.AssertionError = function(options) {
   1.101 +  this.name = "AssertionError";
   1.102 +  this.actual = options.actual;
   1.103 +  this.expected = options.expected;
   1.104 +  this.operator = options.operator;
   1.105 +  this.message = getMessage(this, options.message);
   1.106 +  // The part of the stack that comes from this module is not interesting.
   1.107 +  let stack = Components.stack;
   1.108 +  do {
   1.109 +    stack = stack.caller;
   1.110 +  } while(stack.filename && stack.filename.contains("Assert.jsm"))
   1.111 +  this.stack = stack;
   1.112 +};
   1.113 +
   1.114 +// assert.AssertionError instanceof Error
   1.115 +Assert.AssertionError.prototype = Object.create(Error.prototype, {
   1.116 +  constructor: {
   1.117 +    value: Assert.AssertionError,
   1.118 +    enumerable: false,
   1.119 +    writable: true,
   1.120 +    configurable: true
   1.121 +  }
   1.122 +});
   1.123 +
   1.124 +let proto = Assert.prototype;
   1.125 +
   1.126 +proto._reporter = null;
   1.127 +/**
   1.128 + * Set a custom assertion report handler function. Arguments passed in to this
   1.129 + * function are:
   1.130 + * err (AssertionError|null) An error object when the assertion failed or null
   1.131 + *                           when it passed
   1.132 + * message (string) Message describing the assertion
   1.133 + * stack (stack) Stack trace of the assertion function
   1.134 + *
   1.135 + * Example:
   1.136 + * ```js
   1.137 + * Assert.setReporter(function customReporter(err, message, stack) {
   1.138 + *   if (err) {
   1.139 + *     do_report_result(false, err.message, err.stack);
   1.140 + *   } else {
   1.141 + *     do_report_result(true, message, stack);
   1.142 + *   }
   1.143 + * });
   1.144 + * ```
   1.145 + *
   1.146 + * @param reporterFunc
   1.147 + *        (function) Report handler function
   1.148 + */
   1.149 +proto.setReporter = function(reporterFunc) {
   1.150 +  this._reporter = reporterFunc;
   1.151 +};
   1.152 +
   1.153 +/**
   1.154 + * 3. All of the following functions must throw an AssertionError when a
   1.155 + * corresponding condition is not met, with a message that may be undefined if
   1.156 + * not provided.  All assertion methods provide both the actual and expected
   1.157 + * values to the assertion error for display purposes.
   1.158 + *
   1.159 + * This report method only throws errors on assertion failures, as per spec,
   1.160 + * but consumers of this module (think: xpcshell-test, mochitest) may want to
   1.161 + * override this default implementation.
   1.162 + *
   1.163 + * Example:
   1.164 + * ```js
   1.165 + * // The following will report an assertion failure.
   1.166 + * this.report(1 != 2, 1, 2, "testing JS number math!", "==");
   1.167 + * ```
   1.168 + *
   1.169 + * @param failed
   1.170 + *        (boolean) Indicates if the assertion failed or not
   1.171 + * @param actual
   1.172 + *        (mixed) The result of evaluating the assertion
   1.173 + * @param expected (optional)
   1.174 + *        (mixed) Expected result from the test author
   1.175 + * @param message (optional)
   1.176 + *        (string) Short explanation of the expected result
   1.177 + * @param operator (optional)
   1.178 + *        (string) Operation qualifier used by the assertion method (ex: '==')
   1.179 + */
   1.180 +proto.report = function(failed, actual, expected, message, operator) {
   1.181 +  let err = new Assert.AssertionError({
   1.182 +    message: message,
   1.183 +    actual: actual,
   1.184 +    expected: expected,
   1.185 +    operator: operator
   1.186 +  });
   1.187 +  if (!this._reporter) {
   1.188 +    // If no custom reporter is set, throw the error.
   1.189 +    if (failed) {
   1.190 +      throw err;
   1.191 +    }
   1.192 +  } else {
   1.193 +    this._reporter(failed ? err : null, message, err.stack);
   1.194 +  }
   1.195 +};
   1.196 +
   1.197 +/**
   1.198 + * 4. Pure assertion tests whether a value is truthy, as determined by !!guard.
   1.199 + * assert.ok(guard, message_opt);
   1.200 + * This statement is equivalent to assert.equal(true, !!guard, message_opt);.
   1.201 + * To test strictly for the value true, use assert.strictEqual(true, guard,
   1.202 + * message_opt);.
   1.203 + *
   1.204 + * @param value
   1.205 + *        (mixed) Test subject to be evaluated as truthy
   1.206 + * @param message (optional)
   1.207 + *        (string) Short explanation of the expected result
   1.208 + */
   1.209 +proto.ok = function(value, message) {
   1.210 +  this.report(!value, value, true, message, "==");
   1.211 +};
   1.212 +
   1.213 +/**
   1.214 + * 5. The equality assertion tests shallow, coercive equality with ==.
   1.215 + * assert.equal(actual, expected, message_opt);
   1.216 + *
   1.217 + * @param actual
   1.218 + *        (mixed) Test subject to be evaluated as equivalent to `expected`
   1.219 + * @param expected
   1.220 + *        (mixed) Test reference to evaluate against `actual`
   1.221 + * @param message (optional)
   1.222 + *        (string) Short explanation of the expected result
   1.223 + */
   1.224 +proto.equal = function equal(actual, expected, message) {
   1.225 +  this.report(actual != expected, actual, expected, message, "==");
   1.226 +};
   1.227 +
   1.228 +/**
   1.229 + * 6. The non-equality assertion tests for whether two objects are not equal
   1.230 + * with != assert.notEqual(actual, expected, message_opt);
   1.231 + *
   1.232 + * @param actual
   1.233 + *        (mixed) Test subject to be evaluated as NOT equivalent to `expected`
   1.234 + * @param expected
   1.235 + *        (mixed) Test reference to evaluate against `actual`
   1.236 + * @param message (optional)
   1.237 + *        (string) Short explanation of the expected result
   1.238 + */
   1.239 +proto.notEqual = function notEqual(actual, expected, message) {
   1.240 +  this.report(actual == expected, actual, expected, message, "!=");
   1.241 +};
   1.242 +
   1.243 +/**
   1.244 + * 7. The equivalence assertion tests a deep equality relation.
   1.245 + * assert.deepEqual(actual, expected, message_opt);
   1.246 + *
   1.247 + * We check using the most exact approximation of equality between two objects
   1.248 + * to keep the chance of false positives to a minimum.
   1.249 + * `JSON.stringify` is not designed to be used for this purpose; objects may
   1.250 + * have ambiguous `toJSON()` implementations that would influence the test.
   1.251 + *
   1.252 + * @param actual
   1.253 + *        (mixed) Test subject to be evaluated as equivalent to `expected`, including nested properties
   1.254 + * @param expected
   1.255 + *        (mixed) Test reference to evaluate against `actual`
   1.256 + * @param message (optional)
   1.257 + *        (string) Short explanation of the expected result
   1.258 + */
   1.259 +proto.deepEqual = function deepEqual(actual, expected, message) {
   1.260 +  this.report(!_deepEqual(actual, expected), actual, expected, message, "deepEqual");
   1.261 +};
   1.262 +
   1.263 +function _deepEqual(actual, expected) {
   1.264 +  // 7.1. All identical values are equivalent, as determined by ===.
   1.265 +  if (actual === expected) {
   1.266 +    return true;
   1.267 +  // 7.2. If the expected value is a Date object, the actual value is
   1.268 +  // equivalent if it is also a Date object that refers to the same time.
   1.269 +  } else if (instanceOf(actual, "Date") && instanceOf(expected, "Date")) {
   1.270 +    return actual.getTime() === expected.getTime();
   1.271 +  // 7.3 If the expected value is a RegExp object, the actual value is
   1.272 +  // equivalent if it is also a RegExp object with the same source and
   1.273 +  // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
   1.274 +  } else if (instanceOf(actual, "RegExp") && instanceOf(expected, "RegExp")) {
   1.275 +    return actual.source === expected.source &&
   1.276 +           actual.global === expected.global &&
   1.277 +           actual.multiline === expected.multiline &&
   1.278 +           actual.lastIndex === expected.lastIndex &&
   1.279 +           actual.ignoreCase === expected.ignoreCase;
   1.280 +  // 7.4. Other pairs that do not both pass typeof value == "object",
   1.281 +  // equivalence is determined by ==.
   1.282 +  } else if (typeof actual != "object" && typeof expected != "object") {
   1.283 +    return actual == expected;
   1.284 +  // 7.5 For all other Object pairs, including Array objects, equivalence is
   1.285 +  // determined by having the same number of owned properties (as verified
   1.286 +  // with Object.prototype.hasOwnProperty.call), the same set of keys
   1.287 +  // (although not necessarily the same order), equivalent values for every
   1.288 +  // corresponding key, and an identical 'prototype' property. Note: this
   1.289 +  // accounts for both named and indexed properties on Arrays.
   1.290 +  } else {
   1.291 +    return objEquiv(actual, expected);
   1.292 +  }
   1.293 +}
   1.294 +
   1.295 +function isUndefinedOrNull(value) {
   1.296 +  return value === null || value === undefined;
   1.297 +}
   1.298 +
   1.299 +function isArguments(object) {
   1.300 +  return instanceOf(object, "Arguments");
   1.301 +}
   1.302 +
   1.303 +function objEquiv(a, b) {
   1.304 +  if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
   1.305 +    return false;
   1.306 +  }
   1.307 +  // An identical 'prototype' property.
   1.308 +  if (a.prototype !== b.prototype) {
   1.309 +    return false;
   1.310 +  }
   1.311 +  // Object.keys may be broken through screwy arguments passing. Converting to
   1.312 +  // an array solves the problem.
   1.313 +  if (isArguments(a)) {
   1.314 +    if (!isArguments(b)) {
   1.315 +      return false;
   1.316 +    }
   1.317 +    a = pSlice.call(a);
   1.318 +    b = pSlice.call(b);
   1.319 +    return _deepEqual(a, b);
   1.320 +  }
   1.321 +  let ka, kb, key, i;
   1.322 +  try {
   1.323 +    ka = Object.keys(a);
   1.324 +    kb = Object.keys(b);
   1.325 +  } catch (e) {
   1.326 +    // Happens when one is a string literal and the other isn't
   1.327 +    return false;
   1.328 +  }
   1.329 +  // Having the same number of owned properties (keys incorporates
   1.330 +  // hasOwnProperty)
   1.331 +  if (ka.length != kb.length)
   1.332 +    return false;
   1.333 +  // The same set of keys (although not necessarily the same order),
   1.334 +  ka.sort();
   1.335 +  kb.sort();
   1.336 +  // Equivalent values for every corresponding key, and possibly expensive deep 
   1.337 +  // test
   1.338 +  for (i = ka.length - 1; i >= 0; i--) {
   1.339 +    key = ka[i];
   1.340 +    if (!_deepEqual(a[key], b[key])) {
   1.341 +      return false;
   1.342 +    }
   1.343 +  }
   1.344 +  return true;
   1.345 +}
   1.346 +
   1.347 +/**
   1.348 + * 8. The non-equivalence assertion tests for any deep inequality.
   1.349 + * assert.notDeepEqual(actual, expected, message_opt);
   1.350 + *
   1.351 + * @param actual
   1.352 + *        (mixed) Test subject to be evaluated as NOT equivalent to `expected`, including nested properties
   1.353 + * @param expected
   1.354 + *        (mixed) Test reference to evaluate against `actual`
   1.355 + * @param message (optional)
   1.356 + *        (string) Short explanation of the expected result
   1.357 + */
   1.358 +proto.notDeepEqual = function notDeepEqual(actual, expected, message) {
   1.359 +  this.report(_deepEqual(actual, expected), actual, expected, message, "notDeepEqual");
   1.360 +};
   1.361 +
   1.362 +/**
   1.363 + * 9. The strict equality assertion tests strict equality, as determined by ===.
   1.364 + * assert.strictEqual(actual, expected, message_opt);
   1.365 + *
   1.366 + * @param actual
   1.367 + *        (mixed) Test subject to be evaluated as strictly equivalent to `expected`
   1.368 + * @param expected
   1.369 + *        (mixed) Test reference to evaluate against `actual`
   1.370 + * @param message (optional)
   1.371 + *        (string) Short explanation of the expected result
   1.372 + */
   1.373 +proto.strictEqual = function strictEqual(actual, expected, message) {
   1.374 +  this.report(actual !== expected, actual, expected, message, "===");
   1.375 +};
   1.376 +
   1.377 +/**
   1.378 + * 10. The strict non-equality assertion tests for strict inequality, as
   1.379 + * determined by !==.  assert.notStrictEqual(actual, expected, message_opt);
   1.380 + *
   1.381 + * @param actual
   1.382 + *        (mixed) Test subject to be evaluated as NOT strictly equivalent to `expected`
   1.383 + * @param expected
   1.384 + *        (mixed) Test reference to evaluate against `actual`
   1.385 + * @param message (optional)
   1.386 + *        (string) Short explanation of the expected result
   1.387 + */
   1.388 +proto.notStrictEqual = function notStrictEqual(actual, expected, message) {
   1.389 +  this.report(actual === expected, actual, expected, message, "!==");
   1.390 +};
   1.391 +
   1.392 +function expectedException(actual, expected) {
   1.393 +  if (!actual || !expected) {
   1.394 +    return false;
   1.395 +  }
   1.396 +
   1.397 +  if (instanceOf(expected, "RegExp")) {
   1.398 +    return expected.test(actual);
   1.399 +  } else if (actual instanceof expected) {
   1.400 +    return true;
   1.401 +  } else if (expected.call({}, actual) === true) {
   1.402 +    return true;
   1.403 +  }
   1.404 +
   1.405 +  return false;
   1.406 +}
   1.407 +
   1.408 +/**
   1.409 + * 11. Expected to throw an error:
   1.410 + * assert.throws(block, Error_opt, message_opt);
   1.411 + *
   1.412 + * @param block
   1.413 + *        (function) Function block to evaluate and catch eventual thrown errors
   1.414 + * @param expected (optional)
   1.415 + *        (mixed) Test reference to evaluate against the thrown result from `block`
   1.416 + * @param message (optional)
   1.417 + *        (string) Short explanation of the expected result
   1.418 + */
   1.419 +proto.throws = function(block, expected, message) {
   1.420 +  let actual;
   1.421 +
   1.422 +  if (typeof expected === "string") {
   1.423 +    message = expected;
   1.424 +    expected = null;
   1.425 +  }
   1.426 +
   1.427 +  try {
   1.428 +    block();
   1.429 +  } catch (e) {
   1.430 +    actual = e;
   1.431 +  }
   1.432 +
   1.433 +  message = (expected && expected.name ? " (" + expected.name + ")." : ".") +
   1.434 +            (message ? " " + message : ".");
   1.435 +
   1.436 +  if (!actual) {
   1.437 +    this.report(true, actual, expected, "Missing expected exception" + message);
   1.438 +  }
   1.439 +
   1.440 +  if ((actual && expected && !expectedException(actual, expected))) {
   1.441 +    throw actual;
   1.442 +  }
   1.443 +
   1.444 +  this.report(false, expected, expected, message);
   1.445 +};

mercurial