|
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/. */ |
|
4 |
|
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 |
|
12 |
|
13 "use strict"; |
|
14 |
|
15 this.EXPORTED_SYMBOLS = [ |
|
16 "Assert" |
|
17 ]; |
|
18 |
|
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 }; |
|
31 |
|
32 function instanceOf(object, type) { |
|
33 return Object.prototype.toString.call(object) == "[object " + type + "]"; |
|
34 } |
|
35 |
|
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 } |
|
48 |
|
49 const kTruncateLength = 128; |
|
50 |
|
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 } |
|
58 |
|
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 } |
|
80 |
|
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 }; |
|
110 |
|
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 }); |
|
120 |
|
121 let proto = Assert.prototype; |
|
122 |
|
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 }; |
|
149 |
|
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 }; |
|
193 |
|
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 }; |
|
209 |
|
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 }; |
|
224 |
|
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 }; |
|
239 |
|
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 }; |
|
259 |
|
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 } |
|
291 |
|
292 function isUndefinedOrNull(value) { |
|
293 return value === null || value === undefined; |
|
294 } |
|
295 |
|
296 function isArguments(object) { |
|
297 return instanceOf(object, "Arguments"); |
|
298 } |
|
299 |
|
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 } |
|
343 |
|
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 }; |
|
358 |
|
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 }; |
|
373 |
|
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 }; |
|
388 |
|
389 function expectedException(actual, expected) { |
|
390 if (!actual || !expected) { |
|
391 return false; |
|
392 } |
|
393 |
|
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 } |
|
401 |
|
402 return false; |
|
403 } |
|
404 |
|
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; |
|
418 |
|
419 if (typeof expected === "string") { |
|
420 message = expected; |
|
421 expected = null; |
|
422 } |
|
423 |
|
424 try { |
|
425 block(); |
|
426 } catch (e) { |
|
427 actual = e; |
|
428 } |
|
429 |
|
430 message = (expected && expected.name ? " (" + expected.name + ")." : ".") + |
|
431 (message ? " " + message : "."); |
|
432 |
|
433 if (!actual) { |
|
434 this.report(true, actual, expected, "Missing expected exception" + message); |
|
435 } |
|
436 |
|
437 if ((actual && expected && !expectedException(actual, expected))) { |
|
438 throw actual; |
|
439 } |
|
440 |
|
441 this.report(false, expected, expected, message); |
|
442 }; |