1 /* ***** BEGIN LICENSE BLOCK *****
  2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3  *
  4  * The contents of this file are subject to the Mozilla Public License Version
  5  * 1.1 (the "License"); you may not use this file except in compliance with
  6  * the License. You may obtain a copy of the License at
  7  * http://www.mozilla.org/MPL/
  8  *
  9  * Software distributed under the License is distributed on an "AS IS" basis,
 10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 11  * for the specific language governing rights and limitations under the
 12  * License.
 13  *
 14  * The Original Code is MozMill Test code.
 15  *
 16  * The Initial Developer of the Original Code is the Mozilla Foundation.
 17  * Portions created by the Initial Developer are Copyright (C) 2011
 18  * the Initial Developer. All Rights Reserved.
 19  *
 20  * Contributor(s):
 21  *   Henrik Skupin <mail@hskupin.info> (Original Author)
 22  *
 23  * Alternatively, the contents of this file may be used under the terms of
 24  * either the GNU General Public License Version 2 or later (the "GPL"), or
 25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 26  * in which case the provisions of the GPL or the LGPL are applicable instead
 27  * of those above. If you wish to allow use of your version of this file only
 28  * under the terms of either the GPL or the LGPL, and not to allow others to
 29  * use your version of this file under the terms of the MPL, indicate your
 30  * decision by deleting the provisions above and replace them with the notice
 31  * and other provisions required by the GPL or the LGPL. If you do not delete
 32  * the provisions above, a recipient may use your version of this file under
 33  * the terms of any one of the MPL, the GPL or the LGPL.
 34  *
 35  * ***** END LICENSE BLOCK ***** */
 36 
 37 /**
 38  * @name assertions
 39  * @namespace Defines expect and assert methods to be used for assertions.
 40  */
 41 var assertions = exports;
 42 
 43 
 44 // Include necessary modules
 45 const { Class } = require("external/inheritance");
 46 const { AssertionError } = require('errors');
 47 
 48 
 49 // Use the frame module of Mozmill to raise non-fatal failures
 50 var mozmillFrame = {};
 51 Cu.import('resource://mozmill/modules/frame.js', mozmillFrame);
 52 
 53 
 54 var Expect = Class.create(
 55 /** @lends assertions.Expect.prototype */
 56 {
 57   /**
 58    * The Expect class implements non-fatal assertions, and can be used in cases
 59    * when a failing test shouldn't abort the current test function. That allows
 60    * to execute multiple tests in a row.
 61    *
 62    * @class Base class for non-fatal assertions
 63    * @constructs
 64    * @param {object} [aStartFrame=Components.stack]
 65    *   Frame to use for logging the test result. If a start frame has been
 66    *   specified, we walk down the stack until a frame with the same filename
 67    *   as the start frame has been found. The next file in the stack will be
 68    *   the frame to use for logging the result.
 69    */
 70   initialize: function Expect_initialize(aStartFrame) {
 71     this._startFrame = aStartFrame;
 72   },
 73 
 74   /**
 75    * Find the frame to use for logging the test result. If a start frame has
 76    * been specified, we walk down the stack until a frame with the same filename
 77    * as the start frame has been found. The next file in the stack will be the
 78    * frame to use for logging the result.
 79    *
 80    * @returns {object} Frame of the stack to use for logging the result.
 81    */
 82   _findCallerFrame: function Expect__findCallerFrame() {
 83     let frame = Components.stack;
 84     let filename = frame.filename.replace(/(.*)-> /, "");
 85 
 86     // If a start frame has been specified, walk up the stack until we have
 87     // found the corresponding file
 88     if (this._startFrame) {
 89       filename = this._startFrame.filename.replace(/(.*)-> /, "");
 90 
 91       while (frame.caller &&
 92              frame.filename && !frame.filename.match(filename)) {
 93         frame = frame.caller;
 94       }
 95     }
 96 
 97     // Walk even up more until the next file has been found
 98     while (frame.caller &&
 99            (!frame.filename || frame.filename.match(filename)))
100       frame = frame.caller;
101     
102     return frame;
103   },
104 
105   /**
106    * Log a test as failing by adding a fail frame.
107    *
108    * @param {object} aResult
109    *   Test result details used for reporting.
110    *   <dl>
111    *     <dd>fileName</dd>
112    *     <dt>Name of the file in which the assertion failed.</dt>
113    *     <dd>function</dd>
114    *     <dt>Function in which the assertion failed.</dt>
115    *     <dd>lineNumber</dd>
116    *     <dt>Line number of the file in which the assertion failed.</dt>
117    *     <dd>message</dd>
118    *     <dt>Message why the assertion failed.</dt>
119    *   </dl>
120    */
121   _logFail: function Expect__logFail(aResult) {
122     mozmillFrame.events.fail({fail: aResult});
123   },
124 
125   /**
126    * Log a test as passing by adding a pass frame.
127    *
128    * @param {object} aResult
129    *   Test result details used for reporting.
130    *   <dl>
131    *     <dd>fileName</dd>
132    *     <dt>Name of the file in which the assertion failed.</dt>
133    *     <dd>function</dd>
134    *     <dt>Function in which the assertion failed.</dt>
135    *     <dd>lineNumber</dd>
136    *     <dt>Line number of the file in which the assertion failed.</dt>
137    *     <dd>message</dd>
138    *     <dt>Message why the assertion failed.</dt>
139    *   </dl>
140    */
141   _logPass: function Expect__logPass(aResult) {
142     mozmillFrame.events.pass({pass: aResult});
143   },
144 
145   /**
146    * Test the condition and mark test as passed or failed
147    *
148    * @param {boolean} aCondition
149    *   Condition to test.
150    * @param {string} aMessage
151    *   Message to show for the test result
152    * @param {string} aDiagnosis
153    *   Diagnose message to show for the test result
154    * @returns {boolean} Result of the test.
155    */
156   _test: function Expect__test(aCondition, aMessage, aDiagnosis) {
157     let diagnosis = aDiagnosis || "";
158     let message = aMessage || "";
159 
160     if (aDiagnosis)
161       message = aMessage ? message + " - " + aDiagnosis : aDiagnosis;
162 
163     // Build result data
164     let frame = this._findCallerFrame();
165     let result = {
166       'fileName'   : frame.filename.replace(/(.*)-> /, ""),
167       'function'   : frame.name,
168       'lineNumber' : frame.lineNumber,
169       'message'    : message
170     };
171 
172     // Log test result
173     if (aCondition)
174       this._logPass(result);
175     else
176       this._logFail(result);
177 
178     return aCondition;
179   },
180 
181   /**
182    * Perform an always passing test
183    *
184    * @param {string} aMessage
185    *   Message to show for the test result.
186    * @returns {boolean} Always returns true.
187    */
188   pass: function Expect_pass(aMessage) {
189     return this._test(true, aMessage, undefined);
190   },
191 
192   /**
193    * Perform an always failing test
194    *
195    * @param {string} aMessage
196    *   Message to show for the test result.
197    * @returns {boolean} Always returns false.
198    */
199   fail: function Expect_fail(aMessage) {
200     return this._test(false, aMessage, undefined);
201   },
202 
203   /**
204    * Test if the value pass
205    *
206    * @param {boolean|string|number|object} aValue
207    *   Value to test.
208    * @param {string} aMessage
209    *   Message to show for the test result.
210    * @returns {boolean} Result of the test.
211    */
212   ok: function Expect_ok(aValue, aMessage) {
213     let condition = !!aValue;
214     let diagnosis = "got '" + aValue + "'";
215   
216     return this._test(condition, aMessage, diagnosis);
217   },
218 
219   /**
220    * Test if both specified values are identical.
221    *
222    * @param {boolean|string|number|object} aValue
223    *   Value to test.
224    * @param {boolean|string|number|object} aExpected
225    *   Value to strictly compare with.
226    * @param {string} aMessage
227    *   Message to show for the test result
228    * @returns {boolean} Result of the test.
229    */
230   equal: function Expect_equal(aValue, aExpected, aMessage) {
231     let condition = (aValue === aExpected);
232     let diagnosis = "got '" + aValue + "', expected '" + aExpected + "'";
233   
234     return this._test(condition, aMessage, diagnosis);
235   },
236 
237   /**
238    * Test if both specified values are not identical.
239    *
240    * @param {boolean|string|number|object} aValue
241    *   Value to test.
242    * @param {boolean|string|number|object} aExpected
243    *   Value to strictly compare with.
244    * @param {string} aMessage
245    *   Message to show for the test result
246    * @returns {boolean} Result of the test.
247    */
248   notEqual: function Expect_notEqual(aValue, aExpected, aMessage) {
249     let condition = (aValue !== aExpected);
250     let diagnosis = "got '" + aValue + "', not expected '" + aExpected + "'";
251 
252     return this._test(condition, aMessage, diagnosis);
253   },
254 
255   /**
256    * Test if the regular expression matches the string.
257    *
258    * @param {string} aString
259    *   String to test.
260    * @param {RegEx} aRegex
261    *   Regular expression to use for testing that a match exists.
262    * @param {string} aMessage
263    *   Message to show for the test result
264    * @returns {boolean} Result of the test.
265    */
266   match: function Expect_match(aString, aRegex, aMessage) {
267     // XXX Bug 634948
268     // Regex objects are transformed to strings when evaluated in a sandbox
269     // For now lets re-create the regex from its string representation
270     let pattern = flags = "";
271     try {
272       let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
273 
274       pattern = matches[1];
275       flags = matches[2];
276     }
277     catch (ex) {
278     }
279 
280     let regex = new RegExp(pattern, flags);
281     let condition = (aString.match(regex) !== null);
282     let diagnosis = "'" + regex + "' matches for '" + aString + "'";
283 
284     return this._test(condition, aMessage, diagnosis);
285   },
286 
287   /**
288    * Test if the regular expression does not match the string.
289    *
290    * @param {string} aString
291    *   String to test.
292    * @param {RegEx} aRegex
293    *   Regular expression to use for testing that a match does not exist.
294    * @param {string} aMessage
295    *   Message to show for the test result
296    * @returns {boolean} Result of the test.
297    */
298   notMatch: function Expect_notMatch(aString, aRegex, aMessage) {
299     // XXX Bug 634948
300     // Regex objects are transformed to strings when evaluated in a sandbox
301     // For now lets re-create the regex from its string representation
302     let pattern = flags = "";
303     try {
304       let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
305 
306       pattern = matches[1];
307       flags = matches[2];
308     }
309     catch (ex) {
310     }
311 
312     let regex = new RegExp(pattern, flags);
313     let condition = (aString.match(regex) === null);
314     let diagnosis = "'" + regex + "' doesn't match for '" + aString + "'";
315 
316     return this._test(condition, aMessage, diagnosis);
317   }
318 });
319 
320 
321 var Assert = Class.extend(Expect,
322 /** @lends assertions.Assert */
323 {
324   /**
325    * The Assert class implements fatal assertions, and can be used in cases
326    * when a failing test has to directly abort the current test function. All
327    * remaining tasks will not be performed.
328    *
329    * @class Base class for fatal assertions
330    * @constructs
331    * @extends assertions.Expect
332    * @requires errors.AssertionError
333    * @param {object} [aStartFrame=Components.stack]
334    *   Frame of the stack to start from for the logging. Per default
335    *   Components.stack should be used when creating an instance of that class.
336    */
337   initialize: function Assert(aStartFrame) {
338     this.parent(aStartFrame);
339   },
340 
341   /**
342    * Log a test as failing by throwing an AssertionException.
343    *
344    * @param {object} aResult
345    *   Test result details used for reporting.
346    *   <dl>
347    *     <dd>fileName</dd>
348    *     <dt>Name of the file in which the assertion failed.</dt>
349    *     <dd>function</dd>
350    *     <dt>Function in which the assertion failed.</dt>
351    *     <dd>lineNumber</dd>
352    *     <dt>Line number of the file in which the assertion failed.</dt>
353    *     <dd>message</dd>
354    *     <dt>Message why the assertion failed.</dt>
355    *   </dl>
356    * @throws {AssertionError }
357    */
358   _logFail: function Assert__logFail(aResult) {
359     throw new AssertionError(aResult);
360   }
361 });
362 
363 
364 // Export of variables
365 assertions.expect = new Expect(Components.stack);
366 assertions.assert = new Assert(Components.stack);
367 
368 // Export of classes
369 assertions.Expect = Expect;
370 assertions.Assert = Assert;
371