1// Copyright 2008 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6//     * Redistributions of source code must retain the above copyright
7//       notice, this list of conditions and the following disclaimer.
8//     * Redistributions in binary form must reproduce the above
9//       copyright notice, this list of conditions and the following
10//       disclaimer in the documentation and/or other materials provided
11//       with the distribution.
12//     * Neither the name of Google Inc. nor the names of its
13//       contributors may be used to endorse or promote products derived
14//       from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28function MjsUnitAssertionError(message) {
29  this.message = message;
30  // This allows fetching the stack trace using TryCatch::StackTrace.
31  this.stack = new Error("").stack;
32}
33
34/*
35 * This file is included in all mini jsunit test cases.  The test
36 * framework expects lines that signal failed tests to start with
37 * the f-word and ignore all other lines.
38 */
39
40
41MjsUnitAssertionError.prototype.toString = function () {
42  return this.message;
43};
44
45
46// Expected and found values the same objects, or the same primitive
47// values.
48// For known primitive values, please use assertEquals.
49var assertSame;
50
51// Expected and found values are identical primitive values or functions
52// or similarly structured objects (checking internal properties
53// of, e.g., Number and Date objects, the elements of arrays
54// and the properties of non-Array objects).
55var assertEquals;
56
57// The found object is an Array with the same length and elements
58// as the expected object. The expected object doesn't need to be an Array,
59// as long as it's "array-ish".
60var assertArrayEquals;
61
62// The found object must have the same enumerable properties as the
63// expected object. The type of object isn't checked.
64var assertPropertiesEqual;
65
66// Assert that the string conversion of the found value is equal to
67// the expected string. Only kept for backwards compatability, please
68// check the real structure of the found value.
69var assertToStringEquals;
70
71// Checks that the found value is true. Use with boolean expressions
72// for tests that doesn't have their own assertXXX function.
73var assertTrue;
74
75// Checks that the found value is false.
76var assertFalse;
77
78// Checks that the found value is null. Kept for historical compatability,
79// please just use assertEquals(null, expected).
80var assertNull;
81
82// Checks that the found value is *not* null.
83var assertNotNull;
84
85// Assert that the passed function or eval code throws an exception.
86// The optional second argument is an exception constructor that the
87// thrown exception is checked against with "instanceof".
88// The optional third argument is a message type string that is compared
89// to the type property on the thrown exception.
90var assertThrows;
91
92// Assert that the passed function or eval code does not throw an exception.
93var assertDoesNotThrow;
94
95// Asserts that the found value is an instance of the constructor passed
96// as the second argument.
97var assertInstanceof;
98
99// Assert that this code is never executed (i.e., always fails if executed).
100var assertUnreachable;
101
102(function () {  // Scope for utility functions.
103
104  function classOf(object) {
105    // Argument must not be null or undefined.
106    var string = Object.prototype.toString.call(object);
107    // String has format [object <ClassName>].
108    return string.substring(8, string.length - 1);
109  }
110
111
112  function PrettyPrint(value) {
113    switch (typeof value) {
114      case "string":
115        return JSON.stringify(value);
116      case "number":
117        if (value === 0 && (1 / value) < 0) return "-0";
118        // FALLTHROUGH.
119      case "boolean":
120      case "undefined":
121      case "function":
122        return String(value);
123      case "object":
124        if (value === null) return "null";
125        var objectClass = classOf(value);
126        switch (objectClass) {
127        case "Number":
128        case "String":
129        case "Boolean":
130        case "Date":
131          return objectClass + "(" + PrettyPrint(value.valueOf()) + ")";
132        case "RegExp":
133          return value.toString();
134        case "Array":
135          return "[" + value.map(PrettyPrintArrayElement).join(",") + "]";
136        case "Object":
137          break;
138        default:
139          return objectClass + "()";
140        }
141        // [[Class]] is "Object".
142        var name = value.constructor.name;
143        if (name) return name + "()";
144        return "Object()";
145      default:
146        return "-- unknown value --";
147    }
148  }
149
150
151  function PrettyPrintArrayElement(value, index, array) {
152    if (value === undefined && !(index in array)) return "";
153    return PrettyPrint(value);
154  }
155
156
157  function fail(expectedText, found, name_opt) {
158    var message = "Fail" + "ure";
159    if (name_opt) {
160      // Fix this when we ditch the old test runner.
161      message += " (" + name_opt + ")";
162    }
163
164    message += ": expected <" + expectedText +
165        "> found <" + PrettyPrint(found) + ">";
166    throw new MjsUnitAssertionError(message);
167  }
168
169
170  function deepObjectEquals(a, b) {
171    var aProps = Object.keys(a);
172    aProps.sort();
173    var bProps = Object.keys(b);
174    bProps.sort();
175    if (!deepEquals(aProps, bProps)) {
176      return false;
177    }
178    for (var i = 0; i < aProps.length; i++) {
179      if (!deepEquals(a[aProps[i]], b[aProps[i]])) {
180        return false;
181      }
182    }
183    return true;
184  }
185
186
187  function deepEquals(a, b) {
188    if (a === b) {
189      // Check for -0.
190      if (a === 0) return (1 / a) === (1 / b);
191      return true;
192    }
193    if (typeof a != typeof b) return false;
194    if (typeof a == "number") return isNaN(a) && isNaN(b);
195    if (typeof a !== "object" && typeof a !== "function") return false;
196    // Neither a nor b is primitive.
197    var objectClass = classOf(a);
198    if (objectClass !== classOf(b)) return false;
199    if (objectClass === "RegExp") {
200      // For RegExp, just compare pattern and flags using its toString.
201      return (a.toString() === b.toString());
202    }
203    // Functions are only identical to themselves.
204    if (objectClass === "Function") return false;
205    if (objectClass === "Array") {
206      var elementCount = 0;
207      if (a.length != b.length) {
208        return false;
209      }
210      for (var i = 0; i < a.length; i++) {
211        if (!deepEquals(a[i], b[i])) return false;
212      }
213      return true;
214    }
215    if (objectClass == "String" || objectClass == "Number" ||
216      objectClass == "Boolean" || objectClass == "Date") {
217      if (a.valueOf() !== b.valueOf()) return false;
218    }
219    return deepObjectEquals(a, b);
220  }
221
222
223  assertSame = function assertSame(expected, found, name_opt) {
224    // TODO(mstarzinger): We should think about using Harmony's egal operator
225    // or the function equivalent Object.is() here.
226    if (found === expected) {
227      if (expected !== 0 || (1 / expected) == (1 / found)) return;
228    } else if ((expected !== expected) && (found !== found)) {
229      return;
230    }
231    fail(PrettyPrint(expected), found, name_opt);
232  };
233
234
235  assertEquals = function assertEquals(expected, found, name_opt) {
236    if (!deepEquals(found, expected)) {
237      fail(PrettyPrint(expected), found, name_opt);
238    }
239  };
240
241
242  assertArrayEquals = function assertArrayEquals(expected, found, name_opt) {
243    var start = "";
244    if (name_opt) {
245      start = name_opt + " - ";
246    }
247    assertEquals(expected.length, found.length, start + "array length");
248    if (expected.length == found.length) {
249      for (var i = 0; i < expected.length; ++i) {
250        assertEquals(expected[i], found[i],
251                     start + "array element at index " + i);
252      }
253    }
254  };
255
256
257  assertPropertiesEqual = function assertPropertiesEqual(expected, found,
258                                                         name_opt) {
259    // Check properties only.
260    if (!deepObjectEquals(expected, found)) {
261      fail(expected, found, name_opt);
262    }
263  };
264
265
266  assertToStringEquals = function assertToStringEquals(expected, found,
267                                                       name_opt) {
268    if (expected != String(found)) {
269      fail(expected, found, name_opt);
270    }
271  };
272
273
274  assertTrue = function assertTrue(value, name_opt) {
275    assertEquals(true, value, name_opt);
276  };
277
278
279  assertFalse = function assertFalse(value, name_opt) {
280    assertEquals(false, value, name_opt);
281  };
282
283
284  assertNull = function assertNull(value, name_opt) {
285    if (value !== null) {
286      fail("null", value, name_opt);
287    }
288  };
289
290
291  assertNotNull = function assertNotNull(value, name_opt) {
292    if (value === null) {
293      fail("not null", value, name_opt);
294    }
295  };
296
297
298  assertThrows = function assertThrows(code, type_opt, cause_opt) {
299    var threwException = true;
300    try {
301      if (typeof code == 'function') {
302        code();
303      } else {
304        eval(code);
305      }
306      threwException = false;
307    } catch (e) {
308      if (typeof type_opt == 'function') {
309        assertInstanceof(e, type_opt);
310      }
311      if (arguments.length >= 3) {
312        assertEquals(e.type, cause_opt);
313      }
314      // Success.
315      return;
316    }
317    throw new MjsUnitAssertionError("Did not throw exception");
318  };
319
320
321  assertInstanceof = function assertInstanceof(obj, type) {
322    if (!(obj instanceof type)) {
323      var actualTypeName = null;
324      var actualConstructor = Object.prototypeOf(obj).constructor;
325      if (typeof actualConstructor == "function") {
326        actualTypeName = actualConstructor.name || String(actualConstructor);
327      }
328      fail("Object <" + PrettyPrint(obj) + "> is not an instance of <" +
329               (type.name || type) + ">" +
330               (actualTypeName ? " but of < " + actualTypeName + ">" : ""));
331    }
332  };
333
334
335   assertDoesNotThrow = function assertDoesNotThrow(code, name_opt) {
336    try {
337      if (typeof code == 'function') {
338        code();
339      } else {
340        eval(code);
341      }
342    } catch (e) {
343      fail("threw an exception: ", e.message || e, name_opt);
344    }
345  };
346
347  assertUnreachable = function assertUnreachable(name_opt) {
348    // Fix this when we ditch the old test runner.
349    var message = "Fail" + "ure: unreachable";
350    if (name_opt) {
351      message += " - " + name_opt;
352    }
353    throw new MjsUnitAssertionError(message);
354  };
355
356})();
357
358