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