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
58// The difference between expected and found value is within certain tolerance.
59var assertEqualsDelta;
60
61// The found object is an Array with the same length and elements
62// as the expected object. The expected object doesn't need to be an Array,
63// as long as it's "array-ish".
64var assertArrayEquals;
65
66// The found object must have the same enumerable properties as the
67// expected object. The type of object isn't checked.
68var assertPropertiesEqual;
69
70// Assert that the string conversion of the found value is equal to
71// the expected string. Only kept for backwards compatability, please
72// check the real structure of the found value.
73var assertToStringEquals;
74
75// Checks that the found value is true. Use with boolean expressions
76// for tests that doesn't have their own assertXXX function.
77var assertTrue;
78
79// Checks that the found value is false.
80var assertFalse;
81
82// Checks that the found value is null. Kept for historical compatibility,
83// please just use assertEquals(null, expected).
84var assertNull;
85
86// Checks that the found value is *not* null.
87var assertNotNull;
88
89// Assert that the passed function or eval code throws an exception.
90// The optional second argument is an exception constructor that the
91// thrown exception is checked against with "instanceof".
92// The optional third argument is a message type string that is compared
93// to the type property on the thrown exception.
94var assertThrows;
95
96// Assert that the passed function or eval code does not throw an exception.
97var assertDoesNotThrow;
98
99// Asserts that the found value is an instance of the constructor passed
100// as the second argument.
101var assertInstanceof;
102
103// Assert that this code is never executed (i.e., always fails if executed).
104var assertUnreachable;
105
106// Assert that the function code is (not) optimized.  If "no sync" is passed
107// as second argument, we do not wait for the concurrent optimization thread to
108// finish when polling for optimization status.
109// Only works with --allow-natives-syntax.
110var assertOptimized;
111var assertUnoptimized;
112
113
114(function () {  // Scope for utility functions.
115
116  function classOf(object) {
117    // Argument must not be null or undefined.
118    var string = Object.prototype.toString.call(object);
119    // String has format [object <ClassName>].
120    return string.substring(8, string.length - 1);
121  }
122
123
124  function PrettyPrint(value) {
125    switch (typeof value) {
126      case "string":
127        return JSON.stringify(value);
128      case "number":
129        if (value === 0 && (1 / value) < 0) return "-0";
130        // FALLTHROUGH.
131      case "boolean":
132      case "undefined":
133      case "function":
134        return String(value);
135      case "object":
136        if (value === null) return "null";
137        var objectClass = classOf(value);
138        switch (objectClass) {
139        case "Number":
140        case "String":
141        case "Boolean":
142        case "Date":
143          return objectClass + "(" + PrettyPrint(value.valueOf()) + ")";
144        case "RegExp":
145          return value.toString();
146        case "Array":
147          return "[" + value.map(PrettyPrintArrayElement).join(",") + "]";
148        case "Object":
149          break;
150        default:
151          return objectClass + "()";
152        }
153        // [[Class]] is "Object".
154        var name = value.constructor.name;
155        if (name) return name + "()";
156        return "Object()";
157      default:
158        return "-- unknown value --";
159    }
160  }
161
162
163  function PrettyPrintArrayElement(value, index, array) {
164    if (value === undefined && !(index in array)) return "";
165    return PrettyPrint(value);
166  }
167
168
169  function fail(expectedText, found, name_opt) {
170    var message = "Fail" + "ure";
171    if (name_opt) {
172      // Fix this when we ditch the old test runner.
173      message += " (" + name_opt + ")";
174    }
175
176    message += ": expected <" + expectedText +
177        "> found <" + PrettyPrint(found) + ">";
178    throw new MjsUnitAssertionError(message);
179  }
180
181
182  function deepObjectEquals(a, b) {
183    var aProps = Object.keys(a);
184    aProps.sort();
185    var bProps = Object.keys(b);
186    bProps.sort();
187    if (!deepEquals(aProps, bProps)) {
188      return false;
189    }
190    for (var i = 0; i < aProps.length; i++) {
191      if (!deepEquals(a[aProps[i]], b[aProps[i]])) {
192        return false;
193      }
194    }
195    return true;
196  }
197
198
199  function deepEquals(a, b) {
200    if (a === b) {
201      // Check for -0.
202      if (a === 0) return (1 / a) === (1 / b);
203      return true;
204    }
205    if (typeof a != typeof b) return false;
206    if (typeof a == "number") return isNaN(a) && isNaN(b);
207    if (typeof a !== "object" && typeof a !== "function") return false;
208    // Neither a nor b is primitive.
209    var objectClass = classOf(a);
210    if (objectClass !== classOf(b)) return false;
211    if (objectClass === "RegExp") {
212      // For RegExp, just compare pattern and flags using its toString.
213      return (a.toString() === b.toString());
214    }
215    // Functions are only identical to themselves.
216    if (objectClass === "Function") return false;
217    if (objectClass === "Array") {
218      var elementCount = 0;
219      if (a.length != b.length) {
220        return false;
221      }
222      for (var i = 0; i < a.length; i++) {
223        if (!deepEquals(a[i], b[i])) return false;
224      }
225      return true;
226    }
227    if (objectClass == "String" || objectClass == "Number" ||
228      objectClass == "Boolean" || objectClass == "Date") {
229      if (a.valueOf() !== b.valueOf()) return false;
230    }
231    return deepObjectEquals(a, b);
232  }
233
234  function checkArity(args, arity, name) {
235    if (args.length < arity) {
236      fail(PrettyPrint(arity), args.length,
237           name + " requires " + arity + " or more arguments");
238    }
239  }
240
241  assertSame = function assertSame(expected, found, name_opt) {
242    checkArity(arguments, 2, "assertSame");
243
244    // TODO(mstarzinger): We should think about using Harmony's egal operator
245    // or the function equivalent Object.is() here.
246    if (found === expected) {
247      if (expected !== 0 || (1 / expected) == (1 / found)) return;
248    } else if ((expected !== expected) && (found !== found)) {
249      return;
250    }
251    fail(PrettyPrint(expected), found, name_opt);
252  };
253
254
255  assertEquals = function assertEquals(expected, found, name_opt) {
256    checkArity(arguments, 2, "assertEquals");
257
258    if (!deepEquals(found, expected)) {
259      fail(PrettyPrint(expected), found, name_opt);
260    }
261  };
262
263
264  assertEqualsDelta =
265      function assertEqualsDelta(expected, found, delta, name_opt) {
266    assertTrue(Math.abs(expected - found) <= delta, name_opt);
267  };
268
269
270  assertArrayEquals = function assertArrayEquals(expected, found, name_opt) {
271    var start = "";
272    if (name_opt) {
273      start = name_opt + " - ";
274    }
275    assertEquals(expected.length, found.length, start + "array length");
276    if (expected.length == found.length) {
277      for (var i = 0; i < expected.length; ++i) {
278        assertEquals(expected[i], found[i],
279                     start + "array element at index " + i);
280      }
281    }
282  };
283
284
285  assertPropertiesEqual = function assertPropertiesEqual(expected, found,
286                                                         name_opt) {
287    // Check properties only.
288    if (!deepObjectEquals(expected, found)) {
289      fail(expected, found, name_opt);
290    }
291  };
292
293
294  assertToStringEquals = function assertToStringEquals(expected, found,
295                                                       name_opt) {
296    if (expected != String(found)) {
297      fail(expected, found, name_opt);
298    }
299  };
300
301
302  assertTrue = function assertTrue(value, name_opt) {
303    assertEquals(true, value, name_opt);
304  };
305
306
307  assertFalse = function assertFalse(value, name_opt) {
308    assertEquals(false, value, name_opt);
309  };
310
311
312  assertNull = function assertNull(value, name_opt) {
313    if (value !== null) {
314      fail("null", value, name_opt);
315    }
316  };
317
318
319  assertNotNull = function assertNotNull(value, name_opt) {
320    if (value === null) {
321      fail("not null", value, name_opt);
322    }
323  };
324
325
326  assertThrows = function assertThrows(code, type_opt, cause_opt) {
327    var threwException = true;
328    try {
329      if (typeof code == 'function') {
330        code();
331      } else {
332        eval(code);
333      }
334      threwException = false;
335    } catch (e) {
336      if (typeof type_opt == 'function') {
337        assertInstanceof(e, type_opt);
338      }
339      if (arguments.length >= 3) {
340        assertEquals(e.type, cause_opt);
341      }
342      // Success.
343      return;
344    }
345    throw new MjsUnitAssertionError("Did not throw exception");
346  };
347
348
349  assertInstanceof = function assertInstanceof(obj, type) {
350    if (!(obj instanceof type)) {
351      var actualTypeName = null;
352      var actualConstructor = Object.getPrototypeOf(obj).constructor;
353      if (typeof actualConstructor == "function") {
354        actualTypeName = actualConstructor.name || String(actualConstructor);
355      }
356      fail("Object <" + PrettyPrint(obj) + "> is not an instance of <" +
357               (type.name || type) + ">" +
358               (actualTypeName ? " but of < " + actualTypeName + ">" : ""));
359    }
360  };
361
362
363   assertDoesNotThrow = function assertDoesNotThrow(code, name_opt) {
364    try {
365      if (typeof code == 'function') {
366        code();
367      } else {
368        eval(code);
369      }
370    } catch (e) {
371      fail("threw an exception: ", e.message || e, name_opt);
372    }
373  };
374
375  assertUnreachable = function assertUnreachable(name_opt) {
376    // Fix this when we ditch the old test runner.
377    var message = "Fail" + "ure: unreachable";
378    if (name_opt) {
379      message += " - " + name_opt;
380    }
381    throw new MjsUnitAssertionError(message);
382  };
383
384  var OptimizationStatusImpl = undefined;
385
386  var OptimizationStatus = function(fun, sync_opt) {
387    if (OptimizationStatusImpl === undefined) {
388      try {
389        OptimizationStatusImpl = new Function(
390            "fun", "sync", "return %GetOptimizationStatus(fun, sync);");
391      } catch (e) {
392        throw new Error("natives syntax not allowed");
393      }
394    } else {
395      OptimizationStatusImpl(fun, sync_opt);
396    }
397  }
398
399  assertUnoptimized = function assertUnoptimized(fun, sync_opt, name_opt) {
400    if (sync_opt === undefined) sync_opt = "";
401    assertTrue(OptimizationStatus(fun, sync_opt) != 1, name_opt);
402  }
403
404  assertOptimized = function assertOptimized(fun, sync_opt, name_opt) {
405    if (sync_opt === undefined) sync_opt = "";
406    assertTrue(OptimizationStatus(fun, sync_opt) != 2, name_opt);
407  }
408
409})();
410