Wed, 31 Dec 2014 07:53:36 +0100
Correct small whitespace inconsistency, lost while renaming variables.
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 var EXPORTED_SYMBOLS = ['Assert', 'Expect'];
7 const Cu = Components.utils;
9 Cu.import("resource://gre/modules/Services.jsm");
11 var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
12 var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
13 var stack = {}; Cu.import('resource://mozmill/modules/stack.js', stack);
15 /**
16 * @name assertions
17 * @namespace Defines expect and assert methods to be used for assertions.
18 */
20 /**
21 * The Assert class implements fatal assertions, and can be used in cases
22 * when a failing test has to directly abort the current test function. All
23 * remaining tasks will not be performed.
24 *
25 */
26 var Assert = function () {}
28 Assert.prototype = {
30 // The following deepEquals implementation is from Narwhal under this license:
32 // http://wiki.commonjs.org/wiki/Unit_Testing/1.0
33 //
34 // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
35 //
36 // Originally from narwhal.js (http://narwhaljs.org)
37 // Copyright (c) 2009 Thomas Robinson <280north.com>
38 //
39 // Permission is hereby granted, free of charge, to any person obtaining a copy
40 // of this software and associated documentation files (the 'Software'), to
41 // deal in the Software without restriction, including without limitation the
42 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
43 // sell copies of the Software, and to permit persons to whom the Software is
44 // furnished to do so, subject to the following conditions:
45 //
46 // The above copyright notice and this permission notice shall be included in
47 // all copies or substantial portions of the Software.
48 //
49 // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
50 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
51 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
52 // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
53 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
54 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
56 _deepEqual: function (actual, expected) {
57 // 7.1. All identical values are equivalent, as determined by ===.
58 if (actual === expected) {
59 return true;
61 // 7.2. If the expected value is a Date object, the actual value is
62 // equivalent if it is also a Date object that refers to the same time.
63 } else if (actual instanceof Date && expected instanceof Date) {
64 return actual.getTime() === expected.getTime();
66 // 7.3. Other pairs that do not both pass typeof value == 'object',
67 // equivalence is determined by ==.
68 } else if (typeof actual != 'object' && typeof expected != 'object') {
69 return actual == expected;
71 // 7.4. For all other Object pairs, including Array objects, equivalence is
72 // determined by having the same number of owned properties (as verified
73 // with Object.prototype.hasOwnProperty.call), the same set of keys
74 // (although not necessarily the same order), equivalent values for every
75 // corresponding key, and an identical 'prototype' property. Note: this
76 // accounts for both named and indexed properties on Arrays.
77 } else {
78 return this._objEquiv(actual, expected);
79 }
80 },
82 _objEquiv: function (a, b) {
83 if (a == null || a == undefined || b == null || b == undefined)
84 return false;
85 // an identical 'prototype' property.
86 if (a.prototype !== b.prototype) return false;
88 function isArguments(object) {
89 return Object.prototype.toString.call(object) == '[object Arguments]';
90 }
92 //~~~I've managed to break Object.keys through screwy arguments passing.
93 // Converting to array solves the problem.
94 if (isArguments(a)) {
95 if (!isArguments(b)) {
96 return false;
97 }
98 a = pSlice.call(a);
99 b = pSlice.call(b);
100 return _deepEqual(a, b);
101 }
102 try {
103 var ka = Object.keys(a),
104 kb = Object.keys(b),
105 key, i;
106 } catch (e) {//happens when one is a string literal and the other isn't
107 return false;
108 }
109 // having the same number of owned properties (keys incorporates
110 // hasOwnProperty)
111 if (ka.length != kb.length)
112 return false;
113 //the same set of keys (although not necessarily the same order),
114 ka.sort();
115 kb.sort();
116 //~~~cheap key test
117 for (i = ka.length - 1; i >= 0; i--) {
118 if (ka[i] != kb[i])
119 return false;
120 }
121 //equivalent values for every corresponding key, and
122 //~~~possibly expensive deep test
123 for (i = ka.length - 1; i >= 0; i--) {
124 key = ka[i];
125 if (!this._deepEqual(a[key], b[key])) return false;
126 }
127 return true;
128 },
130 _expectedException : function Assert__expectedException(actual, expected) {
131 if (!actual || !expected) {
132 return false;
133 }
135 if (expected instanceof RegExp) {
136 return expected.test(actual);
137 } else if (actual instanceof expected) {
138 return true;
139 } else if (expected.call({}, actual) === true) {
140 return true;
141 } else if (actual.name === expected.name) {
142 return true;
143 }
145 return false;
146 },
148 /**
149 * Log a test as failing by throwing an AssertionException.
150 *
151 * @param {object} aResult
152 * Test result details used for reporting.
153 * <dl>
154 * <dd>fileName</dd>
155 * <dt>Name of the file in which the assertion failed.</dt>
156 * <dd>functionName</dd>
157 * <dt>Function in which the assertion failed.</dt>
158 * <dd>lineNumber</dd>
159 * <dt>Line number of the file in which the assertion failed.</dt>
160 * <dd>message</dd>
161 * <dt>Message why the assertion failed.</dt>
162 * </dl>
163 * @throws {errors.AssertionError}
164 *
165 */
166 _logFail: function Assert__logFail(aResult) {
167 throw new errors.AssertionError(aResult.message,
168 aResult.fileName,
169 aResult.lineNumber,
170 aResult.functionName,
171 aResult.name);
172 },
174 /**
175 * Log a test as passing by adding a pass frame.
176 *
177 * @param {object} aResult
178 * Test result details used for reporting.
179 * <dl>
180 * <dd>fileName</dd>
181 * <dt>Name of the file in which the assertion failed.</dt>
182 * <dd>functionName</dd>
183 * <dt>Function in which the assertion failed.</dt>
184 * <dd>lineNumber</dd>
185 * <dt>Line number of the file in which the assertion failed.</dt>
186 * <dd>message</dd>
187 * <dt>Message why the assertion failed.</dt>
188 * </dl>
189 */
190 _logPass: function Assert__logPass(aResult) {
191 broker.pass({pass: aResult});
192 },
194 /**
195 * Test the condition and mark test as passed or failed
196 *
197 * @param {boolean} aCondition
198 * Condition to test.
199 * @param {string} aMessage
200 * Message to show for the test result
201 * @param {string} aDiagnosis
202 * Diagnose message to show for the test result
203 * @throws {errors.AssertionError}
204 *
205 * @returns {boolean} Result of the test.
206 */
207 _test: function Assert__test(aCondition, aMessage, aDiagnosis) {
208 let diagnosis = aDiagnosis || "";
209 let message = aMessage || "";
211 if (diagnosis)
212 message = aMessage ? message + " - " + diagnosis : diagnosis;
214 // Build result data
215 let frame = stack.findCallerFrame(Components.stack);
217 let result = {
218 'fileName' : frame.filename.replace(/(.*)-> /, ""),
219 'functionName' : frame.name,
220 'lineNumber' : frame.lineNumber,
221 'message' : message
222 };
224 // Log test result
225 if (aCondition) {
226 this._logPass(result);
227 }
228 else {
229 result.stack = Components.stack;
230 this._logFail(result);
231 }
233 return aCondition;
234 },
236 /**
237 * Perform an always passing test
238 *
239 * @param {string} aMessage
240 * Message to show for the test result.
241 * @returns {boolean} Always returns true.
242 */
243 pass: function Assert_pass(aMessage) {
244 return this._test(true, aMessage, undefined);
245 },
247 /**
248 * Perform an always failing test
249 *
250 * @param {string} aMessage
251 * Message to show for the test result.
252 * @throws {errors.AssertionError}
253 *
254 * @returns {boolean} Always returns false.
255 */
256 fail: function Assert_fail(aMessage) {
257 return this._test(false, aMessage, undefined);
258 },
260 /**
261 * Test if the value pass
262 *
263 * @param {boolean|string|number|object} aValue
264 * Value to test.
265 * @param {string} aMessage
266 * Message to show for the test result.
267 * @throws {errors.AssertionError}
268 *
269 * @returns {boolean} Result of the test.
270 */
271 ok: function Assert_ok(aValue, aMessage) {
272 let condition = !!aValue;
273 let diagnosis = "got '" + aValue + "'";
275 return this._test(condition, aMessage, diagnosis);
276 },
278 /**
279 * Test if both specified values are identical.
280 *
281 * @param {boolean|string|number|object} aValue
282 * Value to test.
283 * @param {boolean|string|number|object} aExpected
284 * Value to strictly compare with.
285 * @param {string} aMessage
286 * Message to show for the test result
287 * @throws {errors.AssertionError}
288 *
289 * @returns {boolean} Result of the test.
290 */
291 equal: function Assert_equal(aValue, aExpected, aMessage) {
292 let condition = (aValue === aExpected);
293 let diagnosis = "'" + aValue + "' should equal '" + aExpected + "'";
295 return this._test(condition, aMessage, diagnosis);
296 },
298 /**
299 * Test if both specified values are not identical.
300 *
301 * @param {boolean|string|number|object} aValue
302 * Value to test.
303 * @param {boolean|string|number|object} aExpected
304 * Value to strictly compare with.
305 * @param {string} aMessage
306 * Message to show for the test result
307 * @throws {errors.AssertionError}
308 *
309 * @returns {boolean} Result of the test.
310 */
311 notEqual: function Assert_notEqual(aValue, aExpected, aMessage) {
312 let condition = (aValue !== aExpected);
313 let diagnosis = "'" + aValue + "' should not equal '" + aExpected + "'";
315 return this._test(condition, aMessage, diagnosis);
316 },
318 /**
319 * Test if an object equals another object
320 *
321 * @param {object} aValue
322 * The object to test.
323 * @param {object} aExpected
324 * The object to strictly compare with.
325 * @param {string} aMessage
326 * Message to show for the test result
327 * @throws {errors.AssertionError}
328 *
329 * @returns {boolean} Result of the test.
330 */
331 deepEqual: function equal(aValue, aExpected, aMessage) {
332 let condition = this._deepEqual(aValue, aExpected);
333 try {
334 var aValueString = JSON.stringify(aValue);
335 } catch (e) {
336 var aValueString = String(aValue);
337 }
338 try {
339 var aExpectedString = JSON.stringify(aExpected);
340 } catch (e) {
341 var aExpectedString = String(aExpected);
342 }
344 let diagnosis = "'" + aValueString + "' should equal '" +
345 aExpectedString + "'";
347 return this._test(condition, aMessage, diagnosis);
348 },
350 /**
351 * Test if an object does not equal another object
352 *
353 * @param {object} aValue
354 * The object to test.
355 * @param {object} aExpected
356 * The object to strictly compare with.
357 * @param {string} aMessage
358 * Message to show for the test result
359 * @throws {errors.AssertionError}
360 *
361 * @returns {boolean} Result of the test.
362 */
363 notDeepEqual: function notEqual(aValue, aExpected, aMessage) {
364 let condition = !this._deepEqual(aValue, aExpected);
365 try {
366 var aValueString = JSON.stringify(aValue);
367 } catch (e) {
368 var aValueString = String(aValue);
369 }
370 try {
371 var aExpectedString = JSON.stringify(aExpected);
372 } catch (e) {
373 var aExpectedString = String(aExpected);
374 }
376 let diagnosis = "'" + aValueString + "' should not equal '" +
377 aExpectedString + "'";
379 return this._test(condition, aMessage, diagnosis);
380 },
382 /**
383 * Test if the regular expression matches the string.
384 *
385 * @param {string} aString
386 * String to test.
387 * @param {RegEx} aRegex
388 * Regular expression to use for testing that a match exists.
389 * @param {string} aMessage
390 * Message to show for the test result
391 * @throws {errors.AssertionError}
392 *
393 * @returns {boolean} Result of the test.
394 */
395 match: function Assert_match(aString, aRegex, aMessage) {
396 // XXX Bug 634948
397 // Regex objects are transformed to strings when evaluated in a sandbox
398 // For now lets re-create the regex from its string representation
399 let pattern = flags = "";
400 try {
401 let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
403 pattern = matches[1];
404 flags = matches[2];
405 } catch (e) {
406 }
408 let regex = new RegExp(pattern, flags);
409 let condition = (aString.match(regex) !== null);
410 let diagnosis = "'" + regex + "' matches for '" + aString + "'";
412 return this._test(condition, aMessage, diagnosis);
413 },
415 /**
416 * Test if the regular expression does not match the string.
417 *
418 * @param {string} aString
419 * String to test.
420 * @param {RegEx} aRegex
421 * Regular expression to use for testing that a match does not exist.
422 * @param {string} aMessage
423 * Message to show for the test result
424 * @throws {errors.AssertionError}
425 *
426 * @returns {boolean} Result of the test.
427 */
428 notMatch: function Assert_notMatch(aString, aRegex, aMessage) {
429 // XXX Bug 634948
430 // Regex objects are transformed to strings when evaluated in a sandbox
431 // For now lets re-create the regex from its string representation
432 let pattern = flags = "";
433 try {
434 let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
436 pattern = matches[1];
437 flags = matches[2];
438 } catch (e) {
439 }
441 let regex = new RegExp(pattern, flags);
442 let condition = (aString.match(regex) === null);
443 let diagnosis = "'" + regex + "' doesn't match for '" + aString + "'";
445 return this._test(condition, aMessage, diagnosis);
446 },
449 /**
450 * Test if a code block throws an exception.
451 *
452 * @param {string} block
453 * function to call to test for exception
454 * @param {RegEx} error
455 * the expected error class
456 * @param {string} message
457 * message to present if assertion fails
458 * @throws {errors.AssertionError}
459 *
460 * @returns {boolean} Result of the test.
461 */
462 throws : function Assert_throws(block, /*optional*/error, /*optional*/message) {
463 return this._throws.apply(this, [true].concat(Array.prototype.slice.call(arguments)));
464 },
466 /**
467 * Test if a code block doesn't throw an exception.
468 *
469 * @param {string} block
470 * function to call to test for exception
471 * @param {RegEx} error
472 * the expected error class
473 * @param {string} message
474 * message to present if assertion fails
475 * @throws {errors.AssertionError}
476 *
477 * @returns {boolean} Result of the test.
478 */
479 doesNotThrow : function Assert_doesNotThrow(block, /*optional*/error, /*optional*/message) {
480 return this._throws.apply(this, [false].concat(Array.prototype.slice.call(arguments)));
481 },
483 /* Tests whether a code block throws the expected exception
484 class. helper for throws() and doesNotThrow()
486 adapted from node.js's assert._throws()
487 https://github.com/joyent/node/blob/master/lib/assert.js
488 */
489 _throws : function Assert__throws(shouldThrow, block, expected, message) {
490 var actual;
492 if (typeof expected === 'string') {
493 message = expected;
494 expected = null;
495 }
497 try {
498 block();
499 } catch (e) {
500 actual = e;
501 }
503 message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
504 (message ? ' ' + message : '.');
506 if (shouldThrow && !actual) {
507 return this._test(false, message, 'Missing expected exception');
508 }
510 if (!shouldThrow && this._expectedException(actual, expected)) {
511 return this._test(false, message, 'Got unwanted exception');
512 }
514 if ((shouldThrow && actual && expected &&
515 !this._expectedException(actual, expected)) || (!shouldThrow && actual)) {
516 throw actual;
517 }
519 return this._test(true, message);
520 },
522 /**
523 * Test if the string contains the pattern.
524 *
525 * @param {String} aString String to test.
526 * @param {String} aPattern Pattern to look for in the string
527 * @param {String} aMessage Message to show for the test result
528 * @throws {errors.AssertionError}
529 *
530 * @returns {Boolean} Result of the test.
531 */
532 contain: function Assert_contain(aString, aPattern, aMessage) {
533 let condition = (aString.indexOf(aPattern) !== -1);
534 let diagnosis = "'" + aString + "' should contain '" + aPattern + "'";
536 return this._test(condition, aMessage, diagnosis);
537 },
539 /**
540 * Test if the string does not contain the pattern.
541 *
542 * @param {String} aString String to test.
543 * @param {String} aPattern Pattern to look for in the string
544 * @param {String} aMessage Message to show for the test result
545 * @throws {errors.AssertionError}
546 *
547 * @returns {Boolean} Result of the test.
548 */
549 notContain: function Assert_notContain(aString, aPattern, aMessage) {
550 let condition = (aString.indexOf(aPattern) === -1);
551 let diagnosis = "'" + aString + "' should not contain '" + aPattern + "'";
553 return this._test(condition, aMessage, diagnosis);
554 },
556 /**
557 * Waits for the callback evaluates to true
558 *
559 * @param {Function} aCallback
560 * Callback for evaluation
561 * @param {String} aMessage
562 * Message to show for result
563 * @param {Number} aTimeout
564 * Timeout in waiting for evaluation
565 * @param {Number} aInterval
566 * Interval between evaluation attempts
567 * @param {Object} aThisObject
568 * this object
569 * @throws {errors.AssertionError}
570 *
571 * @returns {Boolean} Result of the test.
572 */
573 waitFor: function Assert_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
574 var timeout = aTimeout || 5000;
575 var interval = aInterval || 100;
577 var self = {
578 timeIsUp: false,
579 result: aCallback.call(aThisObject)
580 };
581 var deadline = Date.now() + timeout;
583 function wait() {
584 if (self.result !== true) {
585 self.result = aCallback.call(aThisObject);
586 self.timeIsUp = Date.now() > deadline;
587 }
588 }
590 var hwindow = Services.appShell.hiddenDOMWindow;
591 var timeoutInterval = hwindow.setInterval(wait, interval);
592 var thread = Services.tm.currentThread;
594 while (self.result !== true && !self.timeIsUp) {
595 thread.processNextEvent(true);
597 let type = typeof(self.result);
598 if (type !== 'boolean')
599 throw TypeError("waitFor() callback has to return a boolean" +
600 " instead of '" + type + "'");
601 }
603 hwindow.clearInterval(timeoutInterval);
605 if (self.result !== true && self.timeIsUp) {
606 aMessage = aMessage || arguments.callee.name + ": Timeout exceeded for '" + aCallback + "'";
607 throw new errors.TimeoutError(aMessage);
608 }
610 broker.pass({'function':'assert.waitFor()'});
611 return true;
612 }
613 }
615 /* non-fatal assertions */
616 var Expect = function () {}
618 Expect.prototype = new Assert();
620 /**
621 * Log a test as failing by adding a fail frame.
622 *
623 * @param {object} aResult
624 * Test result details used for reporting.
625 * <dl>
626 * <dd>fileName</dd>
627 * <dt>Name of the file in which the assertion failed.</dt>
628 * <dd>functionName</dd>
629 * <dt>Function in which the assertion failed.</dt>
630 * <dd>lineNumber</dd>
631 * <dt>Line number of the file in which the assertion failed.</dt>
632 * <dd>message</dd>
633 * <dt>Message why the assertion failed.</dt>
634 * </dl>
635 */
636 Expect.prototype._logFail = function Expect__logFail(aResult) {
637 broker.fail({fail: aResult});
638 }
640 /**
641 * Waits for the callback evaluates to true
642 *
643 * @param {Function} aCallback
644 * Callback for evaluation
645 * @param {String} aMessage
646 * Message to show for result
647 * @param {Number} aTimeout
648 * Timeout in waiting for evaluation
649 * @param {Number} aInterval
650 * Interval between evaluation attempts
651 * @param {Object} aThisObject
652 * this object
653 */
654 Expect.prototype.waitFor = function Expect_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
655 let condition = true;
656 let message = aMessage;
658 try {
659 Assert.prototype.waitFor.apply(this, arguments);
660 }
661 catch (ex if ex instanceof errors.AssertionError) {
662 message = ex.message;
663 condition = false;
664 }
666 return this._test(condition, message);
667 }