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 +};