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 + "\n\nStack: " + this.stack;
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 throws an exception.
97// The exception is checked against the second argument using assertEquals.
98var assertThrowsEquals;
99
100// Assert that the passed function or eval code does not throw an exception.
101var assertDoesNotThrow;
102
103// Asserts that the found value is an instance of the constructor passed
104// as the second argument.
105var assertInstanceof;
106
107// Assert that this code is never executed (i.e., always fails if executed).
108var assertUnreachable;
109
110// Assert that the function code is (not) optimized.  If "no sync" is passed
111// as second argument, we do not wait for the concurrent optimization thread to
112// finish when polling for optimization status.
113// Only works with --allow-natives-syntax.
114var assertOptimized;
115var assertUnoptimized;
116
117// Assert that a string contains another expected substring.
118var assertContains;
119
120
121(function () {  // Scope for utility functions.
122
123  var ObjectPrototypeToString = Object.prototype.toString;
124  var NumberPrototypeValueOf = Number.prototype.valueOf;
125  var BooleanPrototypeValueOf = Boolean.prototype.valueOf;
126  var StringPrototypeValueOf = String.prototype.valueOf;
127  var DatePrototypeValueOf = Date.prototype.valueOf;
128  var RegExpPrototypeToString = RegExp.prototype.toString;
129  var ArrayPrototypeMap = Array.prototype.map;
130  var ArrayPrototypeJoin = Array.prototype.join;
131
132  function classOf(object) {
133    // Argument must not be null or undefined.
134    var string = ObjectPrototypeToString.call(object);
135    // String has format [object <ClassName>].
136    return string.substring(8, string.length - 1);
137  }
138
139
140  function ValueOf(value) {
141    switch (classOf(value)) {
142      case "Number":
143        return NumberPrototypeValueOf.call(value);
144      case "String":
145        return StringPrototypeValueOf.call(value);
146      case "Boolean":
147        return BooleanPrototypeValueOf.call(value);
148      case "Date":
149        return DatePrototypeValueOf.call(value);
150      default:
151        return value;
152    }
153  }
154
155
156  function PrettyPrint(value) {
157    switch (typeof value) {
158      case "string":
159        return JSON.stringify(value);
160      case "number":
161        if (value === 0 && (1 / value) < 0) return "-0";
162        // FALLTHROUGH.
163      case "boolean":
164      case "undefined":
165      case "function":
166      case "symbol":
167        return String(value);
168      case "object":
169        if (value === null) return "null";
170        var objectClass = classOf(value);
171        switch (objectClass) {
172          case "Number":
173          case "String":
174          case "Boolean":
175          case "Date":
176            return objectClass + "(" + PrettyPrint(ValueOf(value)) + ")";
177          case "RegExp":
178            return RegExpPrototypeToString.call(value);
179          case "Array":
180            var mapped = ArrayPrototypeMap.call(value, PrettyPrintArrayElement);
181            var joined = ArrayPrototypeJoin.call(mapped, ",");
182            return "[" + joined + "]";
183          case "Object":
184            break;
185          default:
186            return objectClass + "()";
187        }
188        // [[Class]] is "Object".
189        var name = value.constructor.name;
190        if (name) return name + "()";
191        return "Object()";
192      default:
193        return "-- unknown value --";
194    }
195  }
196
197
198  function PrettyPrintArrayElement(value, index, array) {
199    if (value === undefined && !(index in array)) return "";
200    return PrettyPrint(value);
201  }
202
203
204  function fail(expectedText, found, name_opt) {
205    var message = "Fail" + "ure";
206    if (name_opt) {
207      // Fix this when we ditch the old test runner.
208      message += " (" + name_opt + ")";
209    }
210
211    message += ": expected <" + expectedText +
212        "> found <" + PrettyPrint(found) + ">";
213    throw new MjsUnitAssertionError(message);
214  }
215
216
217  function deepObjectEquals(a, b) {
218    var aProps = Object.keys(a);
219    aProps.sort();
220    var bProps = Object.keys(b);
221    bProps.sort();
222    if (!deepEquals(aProps, bProps)) {
223      return false;
224    }
225    for (var i = 0; i < aProps.length; i++) {
226      if (!deepEquals(a[aProps[i]], b[aProps[i]])) {
227        return false;
228      }
229    }
230    return true;
231  }
232
233
234  function deepEquals(a, b) {
235    if (a === b) {
236      // Check for -0.
237      if (a === 0) return (1 / a) === (1 / b);
238      return true;
239    }
240    if (typeof a !== typeof b) return false;
241    if (typeof a === "number") return isNaN(a) && isNaN(b);
242    if (typeof a !== "object" && typeof a !== "function") return false;
243    // Neither a nor b is primitive.
244    var objectClass = classOf(a);
245    if (objectClass !== classOf(b)) return false;
246    if (objectClass === "RegExp") {
247      // For RegExp, just compare pattern and flags using its toString.
248      return RegExpPrototypeToString.call(a) ===
249             RegExpPrototypeToString.call(b);
250    }
251    // Functions are only identical to themselves.
252    if (objectClass === "Function") return false;
253    if (objectClass === "Array") {
254      var elementCount = 0;
255      if (a.length !== b.length) {
256        return false;
257      }
258      for (var i = 0; i < a.length; i++) {
259        if (!deepEquals(a[i], b[i])) return false;
260      }
261      return true;
262    }
263    if (objectClass === "String" || objectClass === "Number" ||
264      objectClass === "Boolean" || objectClass === "Date") {
265      if (ValueOf(a) !== ValueOf(b)) return false;
266    }
267    return deepObjectEquals(a, b);
268  }
269
270  assertSame = function assertSame(expected, found, name_opt) {
271    // TODO(mstarzinger): We should think about using Harmony's egal operator
272    // or the function equivalent Object.is() here.
273    if (found === expected) {
274      if (expected !== 0 || (1 / expected) === (1 / found)) return;
275    } else if ((expected !== expected) && (found !== found)) {
276      return;
277    }
278    fail(PrettyPrint(expected), found, name_opt);
279  };
280
281
282  assertEquals = function assertEquals(expected, found, name_opt) {
283    if (!deepEquals(found, expected)) {
284      fail(PrettyPrint(expected), found, name_opt);
285    }
286  };
287
288
289  assertEqualsDelta =
290      function assertEqualsDelta(expected, found, delta, name_opt) {
291    assertTrue(Math.abs(expected - found) <= delta, name_opt);
292  };
293
294
295  assertArrayEquals = function assertArrayEquals(expected, found, name_opt) {
296    var start = "";
297    if (name_opt) {
298      start = name_opt + " - ";
299    }
300    assertEquals(expected.length, found.length, start + "array length");
301    if (expected.length === found.length) {
302      for (var i = 0; i < expected.length; ++i) {
303        assertEquals(expected[i], found[i],
304                     start + "array element at index " + i);
305      }
306    }
307  };
308
309
310  assertPropertiesEqual = function assertPropertiesEqual(expected, found,
311                                                         name_opt) {
312    // Check properties only.
313    if (!deepObjectEquals(expected, found)) {
314      fail(expected, found, name_opt);
315    }
316  };
317
318
319  assertToStringEquals = function assertToStringEquals(expected, found,
320                                                       name_opt) {
321    if (expected !== String(found)) {
322      fail(expected, found, name_opt);
323    }
324  };
325
326
327  assertTrue = function assertTrue(value, name_opt) {
328    assertEquals(true, value, name_opt);
329  };
330
331
332  assertFalse = function assertFalse(value, name_opt) {
333    assertEquals(false, value, name_opt);
334  };
335
336
337  assertNull = function assertNull(value, name_opt) {
338    if (value !== null) {
339      fail("null", value, name_opt);
340    }
341  };
342
343
344  assertNotNull = function assertNotNull(value, name_opt) {
345    if (value === null) {
346      fail("not null", value, name_opt);
347    }
348  };
349
350
351  assertThrows = function assertThrows(code, type_opt, cause_opt) {
352    var threwException = true;
353    try {
354      if (typeof code === 'function') {
355        code();
356      } else {
357        eval(code);
358      }
359      threwException = false;
360    } catch (e) {
361      if (typeof type_opt === 'function') {
362        assertInstanceof(e, type_opt);
363      } else if (type_opt !== void 0) {
364        fail("invalid use of assertThrows, maybe you want assertThrowsEquals");
365      }
366      if (arguments.length >= 3) {
367        assertEquals(e.type, cause_opt);
368      }
369      // Success.
370      return;
371    }
372    throw new MjsUnitAssertionError("Did not throw exception");
373  };
374
375
376  assertThrowsEquals = function assertThrowsEquals(fun, val) {
377    try {
378      fun();
379    } catch(e) {
380      assertEquals(val, e);
381      return;
382    }
383    throw new MjsUnitAssertionError("Did not throw exception");
384  };
385
386
387  assertInstanceof = function assertInstanceof(obj, type) {
388    if (!(obj instanceof type)) {
389      var actualTypeName = null;
390      var actualConstructor = Object.getPrototypeOf(obj).constructor;
391      if (typeof actualConstructor === "function") {
392        actualTypeName = actualConstructor.name || String(actualConstructor);
393      }
394      fail("Object <" + PrettyPrint(obj) + "> is not an instance of <" +
395               (type.name || type) + ">" +
396               (actualTypeName ? " but of < " + actualTypeName + ">" : ""));
397    }
398  };
399
400
401   assertDoesNotThrow = function assertDoesNotThrow(code, name_opt) {
402    try {
403      if (typeof code === 'function') {
404        code();
405      } else {
406        eval(code);
407      }
408    } catch (e) {
409      fail("threw an exception: ", e.message || e, name_opt);
410    }
411  };
412
413  assertUnreachable = function assertUnreachable(name_opt) {
414    // Fix this when we ditch the old test runner.
415    var message = "Fail" + "ure: unreachable";
416    if (name_opt) {
417      message += " - " + name_opt;
418    }
419    throw new MjsUnitAssertionError(message);
420  };
421
422  assertContains = function(sub, value, name_opt) {
423    if (value == null ? (sub != null) : value.indexOf(sub) == -1) {
424      fail("contains '" + String(sub) + "'", value, name_opt);
425    }
426  };
427
428  var OptimizationStatusImpl = undefined;
429
430  var OptimizationStatus = function(fun, sync_opt) {
431    if (OptimizationStatusImpl === undefined) {
432      try {
433        OptimizationStatusImpl = new Function(
434            "fun", "sync", "return %GetOptimizationStatus(fun, sync);");
435      } catch (e) {
436        throw new Error("natives syntax not allowed");
437      }
438    }
439    return OptimizationStatusImpl(fun, sync_opt);
440  }
441
442  assertUnoptimized = function assertUnoptimized(fun, sync_opt, name_opt) {
443    if (sync_opt === undefined) sync_opt = "";
444    assertTrue(OptimizationStatus(fun, sync_opt) !== 1, name_opt);
445  }
446
447  assertOptimized = function assertOptimized(fun, sync_opt, name_opt) {
448    if (sync_opt === undefined) sync_opt = "";
449    assertTrue(OptimizationStatus(fun, sync_opt) !== 2, name_opt);
450  }
451
452})();
453