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

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

mercurial