Add ES5 Array methods reduce and reduceRight, with test.
authorlrn@chromium.org <lrn@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 21 Apr 2009 09:57:30 +0000 (09:57 +0000)
committerlrn@chromium.org <lrn@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 21 Apr 2009 09:57:30 +0000 (09:57 +0000)
git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1749 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/array.js
src/messages.js
test/mjsunit/array-reduce.js [new file with mode: 0755]
test/mjsunit/mjsunit.js

index d30a989..0adbf9d 100644 (file)
@@ -879,6 +879,62 @@ function ArrayLastIndexOf(element, index) {
 }
 
 
+function ArrayReduce(callback, current) {
+  if (!IS_FUNCTION(callback)) {
+    throw MakeTypeError('called_non_callable', [callback]);
+  }
+  // Pull out the length so that modifications to the length in the
+  // loop will not affect the looping.
+  var length = this.length;
+  var i = 0;
+
+  find_initial: if (%_ArgumentsLength() < 2) {
+    for (; i < length; i++) {
+      current = this[i];
+      if (!IS_UNDEFINED(current) || i in this) {
+        i++;
+        break find_initial;
+      }
+    }
+    throw MakeTypeError('reduce_no_initial', []);
+  }
+
+  for (; i < length; i++) {
+    var element = this[i];
+    if (!IS_UNDEFINED(element) || i in this) {
+      current = callback.call(null, current, element, i, this);
+    }
+  }
+  return current;
+}
+
+function ArrayReduceRight(callback, current) {
+  if (!IS_FUNCTION(callback)) {
+    throw MakeTypeError('called_non_callable', [callback]);
+  }
+  var i = this.length - 1;
+
+  find_initial: if (%_ArgumentsLength() < 2) {
+    for (; i >= 0; i--) {
+      current = this[i];
+      if (!IS_UNDEFINED(current) || i in this) {
+        i--;
+        break find_initial;
+      }
+    }
+    throw MakeTypeError('reduce_no_initial', []);
+  }
+
+  for (; i >= 0; i--) {
+    var element = this[i];
+    if (!IS_UNDEFINED(element) || i in this) {
+      current = callback.call(null, current, element, i, this);
+    }
+  }
+  return current;
+}
+
+
 // -------------------------------------------------------------------
 
 
@@ -917,7 +973,9 @@ function SetupArray() {
     "every", ArrayEvery,
     "map", ArrayMap,
     "indexOf", ArrayIndexOf,
-    "lastIndexOf", ArrayLastIndexOf
+    "lastIndexOf", ArrayLastIndexOf,
+    "reduce", ArrayReduce,
+    "reduceRight", ArrayReduceRight
   ));
 
   // Manipulate the length of some of the functions to meet
@@ -930,7 +988,9 @@ function SetupArray() {
     ArrayMap: 1,
     ArrayIndexOf: 1,
     ArrayLastIndexOf: 1,
-    ArrayPush: 1
+    ArrayPush: 1,
+    ArrayReduce: 1,
+    ArrayReduceRight: 1
   });
 }
 
index cd9a1e8..fa6fb1f 100644 (file)
@@ -98,6 +98,7 @@ const kMessages = {
   instanceof_function_expected: "Expecting a function in instanceof check, but got %0",
   instanceof_nonobject_proto:   "Function has non-object prototype '%0' in instanceof check",
   null_to_object:               "Cannot convert null to object",
+  reduce_no_initial:            "Reduce of empty array with no initial value",
   // RangeError
   invalid_array_length:         "Invalid array length",
   invalid_array_apply_length:   "Function.prototype.apply supports only up to 1024 arguments",
diff --git a/test/mjsunit/array-reduce.js b/test/mjsunit/array-reduce.js
new file mode 100755 (executable)
index 0000000..12f0602
--- /dev/null
@@ -0,0 +1,490 @@
+// Copyright 2009 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/**
+ * @fileoverview Test reduce and reduceRight
+ */
+
+function clone(v) {
+  // Shallow-copies arrays, returns everything else verbatim.
+  if (v instanceof Array) {
+    // Shallow-copy an array.
+    var newArray = new Array(v.length);
+    for (var i in v) {
+      newArray[i] = v[i];
+    }
+    return newArray;
+  }
+  return v;
+}
+
+
+// Creates a callback function for reduce/reduceRight that tests the number
+// of arguments and otherwise behaves as "func", but which also
+// records all calls in an array on the function (as arrays of arguments
+// followed by result).
+function makeRecorder(func, testName) {
+  var record = [];
+  var f = function recorder(a, b, i, s) {
+    assertEquals(4, arguments.length,
+                 testName + "(number of arguments: " + arguments.length + ")");
+    assertEquals("number", typeof(i), testName + "(index must be number)");
+    assertEquals(s[i], b, testName + "(current argument is at index)");
+    if (record.length > 0) {
+      var prevRecord = record[record.length - 1];
+      var prevResult = prevRecord[prevRecord.length - 1];
+      assertEquals(prevResult, a,
+                   testName + "(prev result -> current input)");
+    }
+    var args = [clone(a), clone(b), i, clone(s)];
+    var result = func.apply(this, arguments);
+    args.push(clone(result));
+    record.push(args);
+    return result;
+  };
+  f.record = record;
+  return f;
+}
+
+
+function testReduce(type,
+                    testName,
+                    expectedResult,
+                    expectedCalls,
+                    array,
+                    combine,
+                    init) {
+  var rec = makeRecorder(combine);
+  var result;
+  var performsCall;
+  if (arguments.length > 6) {
+    result = array[type](rec, init);
+  } else {
+    result = array[type](rec);
+  }
+  var calls = rec.record;
+  assertEquals(expectedCalls.length, calls.length,
+               testName + " (number of calls)");
+  for (var i = 0; i < expectedCalls.length; i++) {
+    assertEquals(expectedCalls[i], calls[i],
+                 testName + " (call " + (i + 1) + ")");
+  }
+  assertEquals(expectedResult, result, testName + " (result)");
+}
+
+
+function sum(a, b) { return a + b; }
+function prod(a, b) { return a * b; }
+function dec(a, b, i, arr) { return a + b * Math.pow(10, arr.length - i - 1); }
+function accumulate(acc, elem, i) { acc[i] = elem; return acc; }
+
+// ---- Test Reduce[Left]
+
+var simpleArray = [2,4,6]
+
+testReduce("reduce", "SimpleReduceSum", 12,
+           [[0, 2, 0, simpleArray, 2],
+            [2, 4, 1, simpleArray, 6],
+            [6, 6, 2, simpleArray, 12]],
+           simpleArray, sum, 0);
+
+testReduce("reduce", "SimpleReduceProd", 48,
+           [[1, 2, 0, simpleArray, 2],
+            [2, 4, 1, simpleArray, 8],
+            [8, 6, 2, simpleArray, 48]],
+           simpleArray, prod, 1);
+
+testReduce("reduce", "SimpleReduceDec", 246,
+           [[0, 2, 0, simpleArray, 200],
+            [200, 4, 1, simpleArray, 240],
+            [240, 6, 2, simpleArray, 246]],
+           simpleArray, dec, 0);
+
+testReduce("reduce", "SimpleReduceAccumulate", simpleArray,
+           [[[], 2, 0, simpleArray, [2]],
+            [[2], 4, 1, simpleArray, [2, 4]],
+            [[2,4], 6, 2, simpleArray, simpleArray]],
+           simpleArray, accumulate, []);
+
+
+testReduce("reduce", "EmptyReduceSum", 0, [], [], sum, 0);
+testReduce("reduce", "EmptyReduceProd", 1, [], [], prod, 1);
+testReduce("reduce", "EmptyReduceDec", 0, [], [], dec, 0);
+testReduce("reduce", "EmptyReduceAccumulate", [], [], [], accumulate, []);
+
+testReduce("reduce", "EmptyReduceSumNoInit", 0, [], [0], sum);
+testReduce("reduce", "EmptyReduceProdNoInit", 1, [], [1], prod);
+testReduce("reduce", "EmptyReduceDecNoInit", 0, [], [0], dec);
+testReduce("reduce", "EmptyReduceAccumulateNoInit", [], [], [[]], accumulate);
+
+
+var simpleSparseArray = [,,,2,,4,,6,,];
+testReduce("reduce", "SimpleSparseReduceSum", 12,
+           [[0, 2, 3, simpleSparseArray, 2],
+            [2, 4, 5, simpleSparseArray, 6],
+            [6, 6, 7, simpleSparseArray, 12]],
+           simpleSparseArray, sum, 0);
+
+testReduce("reduce", "SimpleSparseReduceProd", 48,
+           [[1, 2, 3, simpleSparseArray, 2],
+            [2, 4, 5, simpleSparseArray, 8],
+            [8, 6, 7, simpleSparseArray, 48]],
+           simpleSparseArray, prod, 1);
+
+testReduce("reduce", "SimpleSparseReduceDec", 204060,
+           [[0, 2, 3, simpleSparseArray, 200000],
+            [200000, 4, 5, simpleSparseArray, 204000],
+            [204000, 6, 7, simpleSparseArray, 204060]],
+           simpleSparseArray, dec, 0);
+
+testReduce("reduce", "SimpleSparseReduceAccumulate", [,,,2,,4,,6],
+           [[[], 2, 3, simpleSparseArray, [,,,2]],
+            [[,,,2], 4, 5, simpleSparseArray, [,,,2,,4]],
+            [[,,,2,,4], 6, 7, simpleSparseArray, [,,,2,,4,,6]]],
+           simpleSparseArray, accumulate, []);
+
+
+testReduce("reduce", "EmptySparseReduceSumNoInit", 0, [], [,,0,,], sum);
+testReduce("reduce", "EmptySparseReduceProdNoInit", 1, [], [,,1,,], prod);
+testReduce("reduce", "EmptySparseReduceDecNoInit", 0, [], [,,0,,], dec);
+testReduce("reduce", "EmptySparseReduceAccumulateNoInit",
+           [], [], [,,[],,], accumulate);
+
+
+var verySparseArray = new Array(500000);
+verySparseArray[100000] = 2;
+verySparseArray[250000] = 4;
+verySparseArray[450000] = 6;
+var verySparseSlice2 = verySparseArray.slice(0, 100001);
+var verySparseSlice4 = verySparseArray.slice(0, 250001);
+var verySparseSlice6 = verySparseArray.slice(0, 450001);
+
+testReduce("reduce", "VerySparseReduceSum", 12,
+           [[0, 2, 100000, verySparseArray, 2],
+            [2, 4, 250000, verySparseArray, 6],
+            [6, 6, 450000, verySparseArray, 12]],
+           verySparseArray, sum, 0);
+
+testReduce("reduce", "VerySparseReduceProd", 48,
+           [[1, 2, 100000, verySparseArray, 2],
+            [2, 4, 250000, verySparseArray, 8],
+            [8, 6, 450000, verySparseArray, 48]],
+           verySparseArray, prod, 1);
+
+testReduce("reduce", "VerySparseReduceDec", Infinity,
+           [[0, 2, 100000, verySparseArray, Infinity],
+            [Infinity, 4, 250000, verySparseArray, Infinity],
+            [Infinity, 6, 450000, verySparseArray, Infinity]],
+           verySparseArray, dec, 0);
+
+testReduce("reduce", "SimpleSparseReduceAccumulate",
+           verySparseSlice6,
+           [[[], 2, 100000, verySparseArray, verySparseSlice2],
+            [verySparseSlice2, 4, 250000, verySparseArray, verySparseSlice4],
+            [verySparseSlice4, 6, 450000, verySparseArray, verySparseSlice6]],
+           verySparseArray, accumulate, []);
+
+
+testReduce("reduce", "VerySparseReduceSumNoInit", 12,
+           [[2, 4, 250000, verySparseArray, 6],
+            [6, 6, 450000, verySparseArray, 12]],
+           verySparseArray, sum);
+
+testReduce("reduce", "VerySparseReduceProdNoInit", 48,
+           [[2, 4, 250000, verySparseArray, 8],
+            [8, 6, 450000, verySparseArray, 48]],
+           verySparseArray, prod);
+
+testReduce("reduce", "VerySparseReduceDecNoInit", Infinity,
+           [[2, 4, 250000, verySparseArray, Infinity],
+            [Infinity, 6, 450000, verySparseArray, Infinity]],
+           verySparseArray, dec);
+
+testReduce("reduce", "SimpleSparseReduceAccumulateNoInit",
+           2,
+           [[2, 4, 250000, verySparseArray, 2],
+            [2, 6, 450000, verySparseArray, 2]],
+           verySparseArray, accumulate);
+
+
+// ---- Test ReduceRight
+
+testReduce("reduceRight", "SimpleReduceRightSum", 12,
+           [[0, 6, 2, simpleArray, 6],
+            [6, 4, 1, simpleArray, 10],
+            [10, 2, 0, simpleArray, 12]],
+           simpleArray, sum, 0);
+
+testReduce("reduceRight", "SimpleReduceRightProd", 48,
+           [[1, 6, 2, simpleArray, 6],
+            [6, 4, 1, simpleArray, 24],
+            [24, 2, 0, simpleArray, 48]],
+           simpleArray, prod, 1);
+
+testReduce("reduceRight", "SimpleReduceRightDec", 246,
+           [[0, 6, 2, simpleArray, 6],
+            [6, 4, 1, simpleArray, 46],
+            [46, 2, 0, simpleArray, 246]],
+           simpleArray, dec, 0);
+
+testReduce("reduceRight", "SimpleReduceRightAccumulate", simpleArray,
+           [[[], 6, 2, simpleArray, [,,6]],
+            [[,,6], 4, 1, simpleArray, [,4,6]],
+            [[,4,6], 2, 0, simpleArray, simpleArray]],
+           simpleArray, accumulate, []);
+
+
+testReduce("reduceRight", "EmptyReduceRightSum", 0, [], [], sum, 0);
+testReduce("reduceRight", "EmptyReduceRightProd", 1, [], [], prod, 1);
+testReduce("reduceRight", "EmptyReduceRightDec", 0, [], [], dec, 0);
+testReduce("reduceRight", "EmptyReduceRightAccumulate", [],
+           [], [], accumulate, []);
+
+testReduce("reduceRight", "EmptyReduceRightSumNoInit", 0, [], [0], sum);
+testReduce("reduceRight", "EmptyReduceRightProdNoInit", 1, [], [1], prod);
+testReduce("reduceRight", "EmptyReduceRightDecNoInit", 0, [], [0], dec);
+testReduce("reduceRight", "EmptyReduceRightAccumulateNoInit",
+           [], [], [[]], accumulate);
+
+
+testReduce("reduceRight", "SimpleSparseReduceRightSum", 12,
+           [[0, 6, 7, simpleSparseArray, 6],
+            [6, 4, 5, simpleSparseArray, 10],
+            [10, 2, 3, simpleSparseArray, 12]],
+           simpleSparseArray, sum, 0);
+
+testReduce("reduceRight", "SimpleSparseReduceRightProd", 48,
+           [[1, 6, 7, simpleSparseArray, 6],
+            [6, 4, 5, simpleSparseArray, 24],
+            [24, 2, 3, simpleSparseArray, 48]],
+           simpleSparseArray, prod, 1);
+
+testReduce("reduceRight", "SimpleSparseReduceRightDec", 204060,
+           [[0, 6, 7, simpleSparseArray, 60],
+            [60, 4, 5, simpleSparseArray, 4060],
+            [4060, 2, 3, simpleSparseArray, 204060]],
+           simpleSparseArray, dec, 0);
+
+testReduce("reduceRight", "SimpleSparseReduceRightAccumulate", [,,,2,,4,,6],
+           [[[], 6, 7, simpleSparseArray, [,,,,,,,6]],
+            [[,,,,,,,6], 4, 5, simpleSparseArray, [,,,,,4,,6]],
+            [[,,,,,4,,6], 2, 3, simpleSparseArray, [,,,2,,4,,6]]],
+           simpleSparseArray, accumulate, []);
+
+
+testReduce("reduceRight", "EmptySparseReduceRightSumNoInit",
+           0, [], [,,0,,], sum);
+testReduce("reduceRight", "EmptySparseReduceRightProdNoInit",
+           1, [], [,,1,,], prod);
+testReduce("reduceRight", "EmptySparseReduceRightDecNoInit",
+           0, [], [,,0,,], dec);
+testReduce("reduceRight", "EmptySparseReduceRightAccumulateNoInit",
+           [], [], [,,[],,], accumulate);
+
+
+var verySparseSuffix6 = new Array(450001);
+verySparseSuffix6[450000] = 6;
+var verySparseSuffix4 = new Array(450001);
+verySparseSuffix4[250000] = 4;
+verySparseSuffix4[450000] = 6;
+var verySparseSuffix2 = verySparseSlice6;
+
+
+testReduce("reduceRight", "VerySparseReduceRightSum", 12,
+           [[0, 6, 450000, verySparseArray, 6],
+            [6, 4, 250000, verySparseArray, 10],
+            [10, 2, 100000, verySparseArray, 12]],
+           verySparseArray, sum, 0);
+
+testReduce("reduceRight", "VerySparseReduceRightProd", 48,
+           [[1, 6, 450000, verySparseArray, 6],
+            [6, 4, 250000, verySparseArray, 24],
+            [24, 2, 100000, verySparseArray, 48]],
+           verySparseArray, prod, 1);
+
+testReduce("reduceRight", "VerySparseReduceRightDec", Infinity,
+           [[0, 6, 450000, verySparseArray, Infinity],
+            [Infinity, 4, 250000, verySparseArray, Infinity],
+            [Infinity, 2, 100000, verySparseArray, Infinity]],
+           verySparseArray, dec, 0);
+
+testReduce("reduceRight", "SimpleSparseReduceRightAccumulate",
+           verySparseSuffix2,
+           [[[], 6, 450000, verySparseArray, verySparseSuffix6],
+            [verySparseSuffix6, 4, 250000, verySparseArray, verySparseSuffix4],
+            [verySparseSuffix4, 2, 100000, verySparseArray, verySparseSuffix2]],
+           verySparseArray, accumulate, []);
+
+
+testReduce("reduceRight", "VerySparseReduceRightSumNoInit", 12,
+           [[6, 4, 250000, verySparseArray, 10],
+            [10, 2, 100000, verySparseArray, 12]],
+           verySparseArray, sum);
+
+testReduce("reduceRight", "VerySparseReduceRightProdNoInit", 48,
+           [[6, 4, 250000, verySparseArray, 24],
+            [24, 2, 100000, verySparseArray, 48]],
+           verySparseArray, prod);
+
+testReduce("reduceRight", "VerySparseReduceRightDecNoInit", Infinity,
+           [[6, 4, 250000, verySparseArray, Infinity],
+            [Infinity, 2, 100000, verySparseArray, Infinity]],
+           verySparseArray, dec);
+
+testReduce("reduceRight", "SimpleSparseReduceRightAccumulateNoInit",
+           6,
+           [[6, 4, 250000, verySparseArray, 6],
+            [6, 2, 100000, verySparseArray, 6]],
+           verySparseArray, accumulate);
+
+
+// undefined is an element
+var undefArray = [,,undefined,,undefined,,];
+
+testReduce("reduce", "SparseUndefinedReduceAdd", NaN,
+           [[0, undefined, 2, undefArray, NaN],
+            [NaN, undefined, 4, undefArray, NaN],
+           ],
+           undefArray, sum, 0);
+
+testReduce("reduceRight", "SparseUndefinedReduceRightAdd", NaN,
+           [[0, undefined, 4, undefArray, NaN],
+            [NaN, undefined, 2, undefArray, NaN],
+           ], undefArray, sum, 0);
+
+testReduce("reduce", "SparseUndefinedReduceAddNoInit", NaN,
+           [[undefined, undefined, 4, undefArray, NaN],
+           ], undefArray, sum);
+
+testReduce("reduceRight", "SparseUndefinedReduceRightAddNoInit", NaN,
+           [[undefined, undefined, 2, undefArray, NaN],
+           ], undefArray, sum);
+
+
+// Test error conditions
+
+try {
+  [1].reduce("not a function");
+  fail("Reduce callback not a function not throwing");
+} catch (e) {
+  assertTrue(e instanceof TypeError,
+             "reduce callback not a function not throwing TypeError");
+  assertEquals("called_non_callable", e.type,
+               "reduce non function TypeError type");
+}
+
+try {
+  [1].reduceRight("not a function");
+  fail("ReduceRight callback not a function not throwing");
+} catch (e) {
+  assertTrue(e instanceof TypeError,
+             "reduceRight callback not a function not throwing TypeError");
+  assertEquals("called_non_callable", e.type,
+               "reduceRight non function TypeError type");
+}
+
+
+try {
+  [].reduce(sum);
+  fail("Reduce no initial value not throwing");
+} catch (e) {
+  assertTrue(e instanceof TypeError,
+             "reduce no initial value not throwing TypeError");
+  assertEquals("reduce_no_initial", e.type,
+               "reduce no initial TypeError type");
+}
+
+try {
+  [].reduceRight(sum);
+  fail("ReduceRight no initial value not throwing");
+} catch (e) {
+  assertTrue(e instanceof TypeError,
+             "reduceRight no initial value not throwing TypeError");
+  assertEquals("reduce_no_initial", e.type,
+               "reduceRight no initial TypeError type");
+}
+
+try {
+  [,,,].reduce(sum);
+  fail("Reduce sparse no initial value not throwing");
+} catch (e) {
+  assertTrue(e instanceof TypeError,
+             "reduce sparse no initial value not throwing TypeError");
+  assertEquals("reduce_no_initial", e.type,
+               "reduce no initial TypeError type");
+
+}
+
+try {
+  [,,,].reduceRight(sum);
+  fail("ReduceRight sparse no initial value not throwing");
+} catch (e) {
+  assertTrue(e instanceof TypeError,
+             "reduceRight sparse no initial value not throwing TypeError");
+  assertEquals("reduce_no_initial", e.type,
+               "reduceRight no initial TypeError type");
+}
+
+
+// Array changing length
+
+function manipulator(a, b, i, s) {
+  if (s.length % 2) {
+    s[s.length * 3] = i;
+  } else {
+    s.length = s.length >> 1;
+  }
+  return a + b;
+}
+
+var arr = [1, 2, 3, 4];
+testReduce("reduce", "ArrayManipulationShort", 3,
+           [[0, 1, 0, [1, 2, 3, 4], 1],
+            [1, 2, 1, [1, 2], 3],
+           ], arr, manipulator, 0);
+
+var arr = [1, 2, 3, 4, 5];
+testReduce("reduce", "ArrayManipulationLonger", 10,
+           [[0, 1, 0, [1, 2, 3, 4, 5], 1],
+            [1, 2, 1, [1, 2, 3, 4, 5,,,,,,,,,,, 0], 3],
+            [3, 3, 2, [1, 2, 3, 4, 5,,,,], 6],
+            [6, 4, 3, [1, 2, 3, 4], 10],
+           ], arr, manipulator, 0);
+
+function extender(a, b, i, s) {
+  s[s.length] = s.length;
+  return a + b;
+}
+
+var arr = [1, 2, 3, 4];
+testReduce("reduce", "ArrayManipulationExtender", 10,
+           [[0, 1, 0, [1, 2, 3, 4], 1],
+            [1, 2, 1, [1, 2, 3, 4, 4], 3],
+            [3, 3, 2, [1, 2, 3, 4, 4, 5], 6],
+            [6, 4, 3, [1, 2, 3, 4, 4, 5, 6], 10],
+           ], arr, extender, 0);
+
index 370d491..320e8d1 100644 (file)
@@ -53,6 +53,9 @@ function fail(expected, found, name_opt) {
 
 function deepEquals(a, b) {
   if (a == b) return true;
+  if (typeof a == "number" && typeof b == "number" && isNaN(a) && isNaN(b)) {
+    return true;
+  }
   if ((typeof a) !== 'object' || (typeof b) !== 'object' ||
       (a === null) || (b === null))
     return false;