Wed, 31 Dec 2014 06:55:50 +0100
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 | }; |