<!--
Copyright (c) 2019 The Khronos Group Inc.
Use of this source code is governed by an MIT-style license that can be
found in the LICENSE.txt file.
-->

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../resources/js-test-style.css"/>
<script src="../../js/js-test-pre.js"></script>
<script src="../../js/test-eval.js"></script>
<script src="../../js/tests/typed-array-test-cases.js"></script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>

<script>
"use strict";

description("Verifies the functionality of the new array-like objects in the TypedArray spec");

var currentlyRunning = '';
var allPassed = true;
function running(str) {
  currentlyRunning = str;
}

function output(str) {
  debug(str);
}

function pass() {
  testPassed(currentlyRunning);
}

function fail(str) {
  allPassed = false;
  var exc;
  if (str)
    exc = currentlyRunning + ': ' + str;
  else
    exc = currentlyRunning;
  testFailed(exc);
}

function assertEq(prefix, expected, val) {
  if (expected != val) {
    var str = prefix + ': expected ' + expected + ', got ' + val;
    throw str;
  }
}

function assert(prefix, expected) {
  if (!expected) {
    var str = prefix + ': expected value / true';
    throw str;
  }
}

function printSummary() {
  if (allPassed) {
    debug("Test passed.");
  } else {
    debug("TEST FAILED");
  }
}

var buffer;
var byteLength;
var subBuffer;
var subArray;
function testSlice() {
  function test(subBuf, starts, size) {
    byteLength = size;
    subBuffer = TestEval(subBuf);
    subArray = new Int8Array(subBuffer);
    assertEq(subBuf, subBuffer.byteLength, byteLength);
    for (var i = 0; i < size; ++i)
      assertEq('Element ' + i, starts + i, subArray[i]);
  }

  try {
    running('testSlice');
    buffer = new ArrayBuffer(32);
    var array = new Int8Array(buffer);
    for (var i = 0; i < 32; ++i)
      array[i] = i;

    test("buffer.slice(0)", 0, 32);
    test("buffer.slice(16)", 16, 16);
    test("buffer.slice(24)", 24, 8);
    test("buffer.slice(32)", 32, 0);
    test("buffer.slice(40)", 32, 0);
    test("buffer.slice(80)", 32, 0);

    test("buffer.slice(-8)", 24, 8);
    test("buffer.slice(-16)", 16, 16);
    test("buffer.slice(-24)", 8, 24);
    test("buffer.slice(-32)", 0, 32);
    test("buffer.slice(-40)", 0, 32);
    test("buffer.slice(-80)", 0, 32);

    test("buffer.slice(0, 32)", 0, 32);
    test("buffer.slice(0, 16)", 0, 16);
    test("buffer.slice(8, 24)", 8, 16);
    test("buffer.slice(16, 32)", 16, 16);
    test("buffer.slice(24, 16)", 24, 0);

    test("buffer.slice(16, -8)", 16, 8);
    test("buffer.slice(-20, 30)", 12, 18);

    test("buffer.slice(-8, -20)", 24, 0);
    test("buffer.slice(-20, -8)", 12, 12);
    test("buffer.slice(-40, 16)", 0, 16);
    test("buffer.slice(-40, 40)", 0, 32);
    pass();
  } catch (e) {
    fail(e);
  }
}

function testArrayBufferIsViewMethod() {
  debug('test ArrayBuffer.isView() with various values');

  try {
    if (!ArrayBuffer.isView) {
      testFailed('ArrayBuffer.isView() method does not exist');
    } else {
      testPassed('ArrayBuffer.isView() method exists');

      shouldBe('ArrayBuffer.isView(new Int8Array(1))', 'true');
      shouldBe('ArrayBuffer.isView(new Uint8Array(1))', 'true');
      shouldBe('ArrayBuffer.isView(new Uint8ClampedArray(1))', 'true');
      shouldBe('ArrayBuffer.isView(new Int16Array(1))', 'true');
      shouldBe('ArrayBuffer.isView(new Uint16Array(1))', 'true');
      shouldBe('ArrayBuffer.isView(new Int32Array(1))', 'true');
      shouldBe('ArrayBuffer.isView(new Uint32Array(1))', 'true');
      shouldBe('ArrayBuffer.isView(new Float32Array(1))', 'true');
      shouldBe('ArrayBuffer.isView(new Float64Array(1))', 'true');
      shouldBe('ArrayBuffer.isView(new DataView(new ArrayBuffer(8)))', 'true');

      shouldBe('ArrayBuffer.isView(undefined)', 'false');
      shouldBe('ArrayBuffer.isView(null)', 'false');
      shouldBe('ArrayBuffer.isView(true)', 'false');
      shouldBe('ArrayBuffer.isView(false)', 'false');
      shouldBe('ArrayBuffer.isView(0)', 'false');
      shouldBe('ArrayBuffer.isView(1)', 'false');
      shouldBe('ArrayBuffer.isView(1.0)', 'false');
      shouldBe('ArrayBuffer.isView("hello")', 'false');
      shouldBe('ArrayBuffer.isView({})', 'false');
      shouldBe('ArrayBuffer.isView(function() {})', 'false');
      shouldBe('ArrayBuffer.isView(new Array(1))', 'false');
    }
  } catch (e) {
    testFailed('Exception thrown while testing ArrayBuffer.isView method: ' + e);
  }
}

function testInheritanceHierarchy() {
  debug('test inheritance hierarchy of typed array views');

  try {
    var foo = ArrayBufferView;
    testFailed('ArrayBufferView is a typedef and should not be defined');
  } catch (e) {
    testPassed('ArrayBufferView is a typedef and was (correctly) not defined');
  }

  // Uint8ClampedArray inherited from Uint8Array in earlier versions
  // of the typed array specification. Since this is no longer the
  // case, assert the new behavior.
  shouldBe('new Uint8ClampedArray(1) instanceof Uint8Array', 'false');

  if (Object.getPrototypeOf(Int8Array.prototype) == Object.prototype) {
    // ES5 behavior.
    shouldBe('Object.getPrototypeOf(Int8Array.prototype)', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Uint8Array.prototype)', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Uint8ClampedArray.prototype)', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Int16Array.prototype)', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Uint16Array.prototype)', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Int32Array.prototype)', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Uint32Array.prototype)', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Float32Array.prototype)', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Float64Array.prototype)', 'Object.prototype');
  } else {
    // As of ES6, the prototypes for typed array constructors point to an intrinsic object whose internal
    // prototype is Object.prototype. Relevant spec section is 22.2.5.2: TypedArray.prototype.
    shouldBe('Object.getPrototypeOf(Object.getPrototypeOf(Int8Array.prototype))', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Object.getPrototypeOf(Uint8Array.prototype))', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Object.getPrototypeOf(Uint8ClampedArray.prototype))', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Object.getPrototypeOf(Int16Array.prototype))', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Object.getPrototypeOf(Uint16Array.prototype))', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Object.getPrototypeOf(Int32Array.prototype))', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Object.getPrototypeOf(Uint32Array.prototype))', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Object.getPrototypeOf(Float32Array.prototype))', 'Object.prototype');
    shouldBe('Object.getPrototypeOf(Object.getPrototypeOf(Float64Array.prototype))', 'Object.prototype');
  }

  shouldBe('Object.getPrototypeOf(DataView.prototype)', 'Object.prototype');
}

//
// Tests for unsigned array variants
//

function testSetAndGet10To1(type, name) {
  running('test ' + name + ' SetAndGet10To1');
  try {
    var array = new type(10);
    for (var i = 0; i < 10; i++) {
      array[i] = 10 - i;
    }
    for (var i = 0; i < 10; i++) {
      assertEq('Element ' + i, 10 - i, array[i]);
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

function testConstructWithArrayOfUnsignedValues(type, name) {
  running('test ' + name + ' ConstructWithArrayOfUnsignedValues');
  try {
    var array = new type([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);
    assertEq('Array length', 10, array.length);
    for (var i = 0; i < 10; i++) {
      assertEq('Element ' + i, 10 - i, array[i]);
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

function testConstructWithTypedArrayOfUnsignedValues(type, name) {
  running('test ' + name + ' ConstructWithTypedArrayOfUnsignedValues');
  try {
    var tmp = new type([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);
    var array = new type(tmp);
    assertEq('Array length', 10, array.length);
    for (var i = 0; i < 10; i++) {
      assertEq('Element ' + i, 10 - i, array[i]);
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

//
// Tests for signed array variants
//

function testSetAndGetPos10ToNeg10(type, name) {
  running('test ' + name + ' SetAndGetPos10ToNeg10');
  try {
    var array = new type(21);
    for (var i = 0; i < 21; i++) {
      array[i] = 10 - i;
    }
    for (var i = 0; i < 21; i++) {
      assertEq('Element ' + i, 10 - i, array[i]);
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

function testConstructWithArrayOfSignedValues(type, name) {
  running('test ' + name + ' ConstructWithArrayOfSignedValues');
  try {
    var array = new type([10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10]);
    assertEq('Array length', 21, array.length);
    for (var i = 0; i < 21; i++) {
      assertEq('Element ' + i, 10 - i, array[i]);
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

function testConstructWithTypedArrayOfSignedValues(type, name) {
  running('test ' + name + ' ConstructWithTypedArrayOfSignedValues');
  try {
    var tmp = new type([10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10]);
    var array = new type(tmp);
    assertEq('Array length', 21, array.length);
    for (var i = 0; i < 21; i++) {
      assertEq('Element ' + i, 10 - i, array[i]);
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

//
// Test cases for integral types.
// Some JavaScript engines need separate copies of this code in order
// to exercise all of their optimized code paths.
//

function testIntegralArrayTruncationBehavior(type, name, unsigned) {
  running('test integral array truncation behavior for ' + name);

  var sourceData;
  var expectedResults;

  if (unsigned) {
    sourceData = [0.6, 10.6, 0.2, 10.2, 10.5, 11.5];
    if (type === Uint8ClampedArray) {
      expectedResults = [1, 11, 0, 10, 10, 12];
    } else {
      expectedResults = [0, 10, 0, 10, 10, 11];
    }
  } else {
    sourceData = [0.6, 10.6, -0.6, -10.6];
    expectedResults = [0, 10, 0, -10];
  }

  var numIterations = 10;
  var array = new type(numIterations);

  // The code block in each of the case statements below is identical, but some
  // JavaScript engines need separate copies in order to exercise all of
  // their optimized code paths.

  try {
    switch (type) {
    case Int8Array:
      for (var ii = 0; ii < sourceData.length; ++ii) {
        for (var jj = 0; jj < numIterations; ++jj) {
          array[jj] = sourceData[ii];
          assertEq('Storing ' + sourceData[ii], expectedResults[ii], array[jj]);
        }
      }
      break;
    case Int16Array:
      for (var ii = 0; ii < sourceData.length; ++ii) {
        for (var jj = 0; jj < numIterations; ++jj) {
          array[jj] = sourceData[ii];
          assertEq('Storing ' + sourceData[ii], expectedResults[ii], array[jj]);
        }
      }
      break;
    case Int32Array:
      for (var ii = 0; ii < sourceData.length; ++ii) {
        for (var jj = 0; jj < numIterations; ++jj) {
          array[jj] = sourceData[ii];
          assertEq('Storing ' + sourceData[ii], expectedResults[ii], array[jj]);
        }
      }
      break;
    case Uint8Array:
      for (var ii = 0; ii < sourceData.length; ++ii) {
        for (var jj = 0; jj < numIterations; ++jj) {
          array[jj] = sourceData[ii];
          assertEq('Storing ' + sourceData[ii], expectedResults[ii], array[jj]);
        }
      }
      break;
    case Uint8ClampedArray:
      for (var ii = 0; ii < sourceData.length; ++ii) {
        for (var jj = 0; jj < numIterations; ++jj) {
          array[jj] = sourceData[ii];
          assertEq('Storing ' + sourceData[ii], expectedResults[ii], array[jj]);
        }
      }
      break;
    case Uint16Array:
      for (var ii = 0; ii < sourceData.length; ++ii) {
        for (var jj = 0; jj < numIterations; ++jj) {
          array[jj] = sourceData[ii];
          assertEq('Storing ' + sourceData[ii], expectedResults[ii], array[jj]);
        }
      }
      break;
    case Uint32Array:
      for (var ii = 0; ii < sourceData.length; ++ii) {
        for (var jj = 0; jj < numIterations; ++jj) {
          array[jj] = sourceData[ii];
          assertEq('Storing ' + sourceData[ii], expectedResults[ii], array[jj]);
        }
      }
      break;
    default:
      fail("Unhandled type");
      break;
    }

    pass();
  } catch (e) {
    fail(e);
  }
}


//
// Test cases for both signed and unsigned types
//

function testGetWithOutOfRangeIndices(type, name) {
    debug('Testing ' + name + ' GetWithOutOfRangeIndices');
    // See below for declaration of this global variable
    array = new type([2, 3]);
    shouldBeUndefined("array[2]");
    shouldBeUndefined("array[-1]");
    shouldBeUndefined("array[0x20000000]");
}

function testOffsetsAndSizes(type, name, elementSizeInBytes) {
  running('test ' + name + ' OffsetsAndSizes');
  try {
    var len = 10;
    assertEq('type.BYTES_PER_ELEMENT', elementSizeInBytes, type.BYTES_PER_ELEMENT);
    var array = new type(len);
    assert('array.buffer', array.buffer);
    assertEq('array.byteOffset', 0, array.byteOffset);
    assertEq('array.length', len, array.length);
    assertEq('array.byteLength', len * elementSizeInBytes, array.byteLength);
    array = new type(array.buffer, elementSizeInBytes, len - 1);
    assert('array.buffer', array.buffer);
    assertEq('array.byteOffset', elementSizeInBytes, array.byteOffset);
    assertEq('array.length', len - 1, array.length);
    assertEq('array.byteLength', (len - 1) * elementSizeInBytes, array.byteLength);
    pass();
  } catch (e) {
    fail(e);
  }
}

function testSetFromTypedArray(type, name) {
  running('test ' + name + ' SetFromTypedArray');
  try {
    var array = new type(10);
    var array2 = new type(5);
    for (var i = 0; i < 10; i++) {
      assertEq('Element ' + i, 0, array[i]);
    }
    for (var i = 0; i < array2.length; i++) {
      array2[i] = i;
    }
    array.set(array2);
    for (var i = 0; i < array2.length; i++) {
      assertEq('Element ' + i, i, array[i]);
    }
    array.set(array2, 5);
    for (var i = 0; i < array2.length; i++) {
      assertEq('Element ' + i, i, array[5 + i]);
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

function negativeTestSetFromTypedArray(type, name) {
  running('negativeTest ' + name + ' SetFromTypedArray');
  try {
    var array = new type(5);
    var array2 = new type(6);
    for (var i = 0; i < 5; i++) {
      assertEq('Element ' + i, 0, array[i]);
    }
    for (var i = 0; i < array2.length; i++) {
      array2[i] = i;
    }
    try {
      array.set(array2);
      fail('Expected exception from array.set(array2)');
      return;
    } catch (e) {
    }
    try {
      array2.set(array, 2);
      fail('Expected exception from array2.set(array, 2)');
      return;
    } catch (e) {
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

function testSetFromArray(type, name) {
  running('test ' + name + ' SetFromArray');
  try {
    var array = new type(10);
    var array2 = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
    for (var i = 0; i < 10; i++) {
      assertEq('Element ' + i, 0, array[i]);
    }
    array.set(array2, 0);
    for (var i = 0; i < array2.length; i++) {
      assertEq('Element ' + i, 10 - i, array[i]);
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

function negativeTestSetFromArray(type, name) {
  running('negativeTest ' + name + ' SetFromArray');
  try {
    var array = new type([2, 3]);
    try {
      array.set([4, 5], 1);
      fail();
      return;
    } catch (e) {
    }
    try {
      array.set([4, 5, 6]);
      fail();
      return;
    } catch (e) {
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

var subarray;
function testSubarray(type, name) {
  running('test ' + name + ' Subarray');
  try {
    var array = new type([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
    var subarray = array.subarray(0, 5);
    assertEq('subarray.length', 5, subarray.length);
    for (var i = 0; i < 5; i++) {
      assertEq('Element ' + i, i, subarray[i]);
    }
    subarray = array.subarray(4, 10);
    assertEq('subarray.length', 6, subarray.length);
    for (var i = 0; i < 6; i++) {
      assertEq('Element ' + i, 4 + i, subarray[i]);
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

function testSubarrayOffsetAndLengthClamping(type, name) {
  running('test ' + name + ' Subarray offset and length clamping');
  try {
    var array = new type([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
    var subarray1 = array.subarray(0, 5);
    var subarray2 = subarray1.subarray(-2, 10);
    assertEq('subarray2.length', 2, subarray2.length);
    assertEq('Element ' + 0, 3, subarray2[0]);
    assertEq('Element ' + 1, 4, subarray2[1]);
    pass();
  } catch (e) {
    fail(e);
  }
}

function negativeTestSubarray(type, name) {
  running('negativeTest ' + name + ' Subarray');
  try {
    var array = new type([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
    subarray = array.subarray(5, 11);
    if (subarray.length != 5) {
      fail();
      return;
    }
    subarray = array.subarray(10, 10);
    if (subarray.length != 0) {
      fail();
      return;
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

function testSetBoundaryConditions(type, name, testValues, expectedValues) {
  running('test ' + name + ' SetBoundaryConditions');
  try {
    var array = new type(1);
    assertEq('Array length', 1, array.length);
    for (var ii = 0; ii < testValues.length; ++ii) {
      for (var jj = 0; jj < 10; ++jj) {
        array[0] = testValues[ii];
        assertEq('Element 0', expectedValues[ii], array[0]);
      }
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

function testConstructionBoundaryConditions(type, name, testValues, expectedValues) {
  running('test ' + name + ' ConstructionBoundaryConditions');
  try {
    var array = new type(testValues);
    assertEq('Array length', testValues.length, array.length);
    for (var ii = 0; ii < testValues.length; ++ii) {
      assertEq('Element ' + ii, expectedValues[ii], array[ii]);
    }
    pass();
  } catch (e) {
    fail(e);
  }
}

function testConstructionWithNullBuffer(type, name) {
    var array;
    try {
        array = new type(null);
        assertEq("Length of " + name + " constructed with null", 0, array.length);
        testPassed("Construction of " + name + " with null produced a " + name + " of length 0");
    } catch (e) {
        // This used to be correct, but TC39 has changed the behavior of these constructors.
        testPassed("Construction of " + name + " with null threw exception");
    }
    try {
        array = new type(null, 0);
        assertEq("Length of " + name + " constructed with null", 0, array.length);
        testPassed("Construction of " + name + " with null produced a " + name + " of length 0");
    } catch (e) {
        // This used to be correct, but TC39 has changed the behavior of these constructors.
        testPassed("Construction of " + name + " with (null, 0) threw exception");
    }
    try {
        array = new type(null, 0, 0);
        assertEq("Length of " + name + " constructed with null", 0, array.length);
        testPassed("Construction of " + name + " with null produced a " + name + " of length 0");
    } catch (e) {
        // This used to be correct, but TC39 has changed the behavior of these constructors.
        testPassed("Construction of " + name + " with (null, 0, 0) threw exception");
    }
}

function testConstructionWithExceptionThrowingObject(type, name) {
    var o = {};
    Object.defineProperty(o, "length", { get: function() { throw "bail;" }});
    try {
        var array = new type(o);
    } catch (e) {
    }
    testPassed("Construction of " + name + " with exception-throwing array-like object didn't crash unexpectedly");
}

function shouldThrowIndexSizeErr(func, text) {
    var errorText = text + " should throw an exception";
    try {
        func();
        testFailed(errorText);
    } catch (e) {
        testPassed(text + " threw an exception");
    }
}

function shouldThrowTypeError(func, text) {
    var ok = false;
    try {
        func();
    } catch (e) {
        if (e instanceof TypeError) {
            ok = true;
        }
    }
    if (ok) {
        testPassed(text + " threw TypeError");
    } else {
        testFailed(text + " should throw TypeError");
    }
}

function testConstructionWithOutOfRangeValues(type, name) {
    shouldThrowIndexSizeErr(function() {
        var buffer = new ArrayBuffer(4);
        var array = new type(buffer, 4, 0x3FFFFFFF);
    }, "Construction of " + name + " with out-of-range number of elements");
    shouldThrowIndexSizeErr(function() {
        var buffer = new ArrayBuffer(4);
        var array = new type(buffer, 8);
    }, "Construction of " + name + " with out-of-range offset");
}

function testConstructionWithNegativeOutOfRangeValues(type, name) {
    try {
        var buffer = new ArrayBuffer(-1);
        testFailed("Construction of ArrayBuffer with negative size should throw exception");
    } catch (e) {
        testPassed("Construction of ArrayBuffer with negative size threw exception");
    }
    try {
        var array = new type(-1);
        testFailed("Construction of " + name + " with negative size should throw exception");
    } catch (e) {
        testPassed("Construction of " + name + " with negative size threw exception");
    }
    shouldThrowIndexSizeErr(function() {
        var buffer = new ArrayBuffer(4);
        var array = new type(buffer, 4, -2147483648);
    }, "Construction of " + name + " with negative out-of-range values");
}

function testConstructionWithUnalignedOffset(type, name, elementSizeInBytes) {
    if (elementSizeInBytes > 1) {
        shouldThrowIndexSizeErr(function() {
            var buffer = new ArrayBuffer(32);
            var array = new type(buffer, 1, elementSizeInBytes);
        }, "Construction of " + name + " with unaligned offset");
    }
}

function testConstructionWithUnalignedLength(type, name, elementSizeInBytes) {
    if (elementSizeInBytes > 1) {
        shouldThrowIndexSizeErr(function() {
            var buffer = new ArrayBuffer(elementSizeInBytes + 1);
            var array = new type(buffer, 0);
        }, "Construction of " + name + " with unaligned length");
    }
}

function testConstructionWithBothArrayBufferAndLength(type, name, elementSizeInBytes) {
    var bufByteLength = 1000 * elementSizeInBytes;
    var buf = new ArrayBuffer(bufByteLength);
    var array1 = new type(buf);
    var array2 = new type(bufByteLength / elementSizeInBytes);
    if (array1.length == array2.length) {
        testPassed("Array lengths matched with explicit and implicit creation of ArrayBuffer");
    } else {
        testFailed("Array lengths DID NOT MATCH with explicit and implicit creation of ArrayBuffer");
    }
}

function testConstructionWithSubPortionOfArrayBuffer(type, name, elementSizeInBytes) {
    if (elementSizeInBytes > 1) {
        // Test construction with a valid sub-portion of an array buffer
        // (whose size is not an integral multiple of the element size).
        var size = 4 * elementSizeInBytes + (elementSizeInBytes / 2);
        var buf = new ArrayBuffer(size);
        try {
            var array = new type(buf, 0, 2);
            testPassed("new " + name + "(new ArrayBuffer(" + size + "), 0, 2) succeeded");
        } catch (e) {
            testFailed("new " + name + "(new ArrayBuffer(" + size + "), 0, 2) failed: " + e);
        }
    }
}

// These need to be global for shouldBe to see them
var array;
var typeSize;

function testSubarrayWithOutOfRangeValues(type, name, sz) {
    debug("Testing subarray of " + name);
    try {
        var buffer = new ArrayBuffer(32);
        array = new type(buffer);
        typeSize = sz;
        shouldBe("array.length", "32 / typeSize");
        try {
            shouldBe("array.subarray(4, 0x3FFFFFFF).length", "(32 / typeSize) - 4");
            shouldBe("array.subarray(4, -2147483648).length", "0");
            // Test subarray() against overflows.
            array = array.subarray(2);
            if (sz > 1) {
                // Full byte offset is +1 larger than the maximum unsigned long int.
                // Make sure subarray() still handles it correctly.  Otherwise overflow would happen and
                // offset would be 0, and array.length array.length would incorrectly be 1.
                var start = 4294967296 / sz - 2;
                array = array.subarray(start, start + 1);
                shouldBe("array.length", "0");
            }
        } catch (e) {
            testFailed("Subarray of " + name + " threw exception");
        }
    } catch (e) {
        testFailed("Exception: " + e);
    }
}

function testSubarrayWithDefaultValues(type, name, sz) {
    debug("Testing subarray with default inputs of " + name);
    try {
        var buffer = new ArrayBuffer(32);
        array = new type(buffer);
        typeSize = sz;
        shouldBe("array.length", "32 / typeSize");
        try {
            shouldBe("array.subarray(0).length", "(32 / typeSize)");
            shouldBe("array.subarray(2).length", "(32 / typeSize) - 2");
            shouldBe("array.subarray(-2).length", "2");
            shouldBe("array.subarray(-2147483648).length", "(32 / typeSize)");
        } catch (e) {
            testFailed("Subarray of " + name + " threw exception");
        }
    } catch (e) {
        testFailed("Exception: " + e);
    }
}

function setWithInvalidOffset(type, name, length,
                              sourceType, sourceName, sourceLength,
                              offset, offsetDescription) {
    var webglArray = new type(length);
    var sourceArray = new sourceType(sourceLength);
    for (var i = 0; i < sourceLength; i++)
        sourceArray[i] = 42 + i;
    try {
        webglArray.set(sourceArray, offset);
        testFailed("Setting " + name + " from " + sourceName + " with " +
                   offsetDescription + " offset was not caught");
    } catch (e) {
        testPassed("Setting " + name + " from " + sourceName + " with " +
                   offsetDescription + " offset was caught");
    }
}

function setWithValidOffset(type, name, length,
                            sourceType, sourceName, sourceLength,
                            offset, offsetDescription) {
    running("Setting " + name + " from " + sourceName + " with " +
            offsetDescription + " offset");
    var webglArray = new type(length);
    var sourceArray = new sourceType(sourceLength);
    for (var i = 0; i < sourceLength; i++)
        sourceArray[i] = 42 + i;
    try {
        webglArray.set(sourceArray, offset);
        offset = Math.floor(offset);
        for (var i = 0; i < sourceLength; i++) {
            assertEq("Element " + i + offset, sourceArray[i], webglArray[i + offset]);
        }
        pass();
    } catch (e) {
        fail(e);
    }
}


function testSettingFromArrayWithOutOfRangeOffset(type, name) {
    setWithInvalidOffset(type, name, 32, Array, "array", 16,
                         0x7FFFFFF8, "out-of-range");
}

function testSettingFromTypedArrayWithOutOfRangeOffset(type, name) {
    setWithInvalidOffset(type, name, 32, type, name, 16,
                         0x7FFFFFF8, "out-of-range");
}

function testSettingFromArrayWithNegativeOffset(type, name) {
    setWithInvalidOffset(type, name, 32, Array, "array", 16,
                         -1, "negative");
}

function testSettingFromTypedArrayWithNegativeOffset(type, name) {
    setWithInvalidOffset(type, name, 32, type, name, 16,
                         -1, "negative");
}

function testSettingFromArrayWithMinusZeroOffset(type, name) {
    setWithValidOffset(type, name, 32, Array, "array", 16,
                       -0, "-0");
}

function testSettingFromTypedArrayWithMinusZeroOffset(type, name) {
    setWithValidOffset(type, name, 32, type, name, 16,
                       -0, "-0");
}

function testSettingFromArrayWithBoundaryOffset(type, name) {
    setWithValidOffset(type, name, 32, Array, "array", 16,
                       16, "boundary");
}

function testSettingFromTypedArrayWithBoundaryOffset(type, name) {
    setWithValidOffset(type, name, 32, type, name, 16,
                       16, "boundary");
}

function testSettingFromArrayWithNonIntegerOffset(type, name) {
    setWithValidOffset(type, name, 32, Array, "array", 16,
                       16.999, "non-integer");
}

function testSettingFromTypedArrayWithNonIntegerOffset(type, name) {
    setWithValidOffset(type, name, 32, type, name, 16,
                       16.999, "non-integer");
}

function testSettingFromFakeArrayWithOutOfRangeLength(type, name) {
    var webglArray = new type(32);
    var array = {};
    array.length = 0x80000000;
    try {
        webglArray.set(array, 8);
        testFailed("Setting " + name + " from fake array with invalid length was not caught");
    } catch (e) {
        testPassed("Setting " + name + " from fake array with invalid length was caught");
    }
}


function negativeTestGetAndSetMethods(type, name) {
    array = new type([2, 3]);
    shouldBeUndefined("array.get");
    var exceptionThrown = false;
    // We deliberately check for an exception here rather than using
    // shouldThrow here because the precise contents of the syntax
    // error are not specified.
    try {
        webGLArray.set(0, 1);
    } catch (e) {
        exceptionThrown = true;
    }
    var output = "array.set(0, 1) ";
    if (exceptionThrown) {
        testPassed(output + "threw exception.");
    } else {
        testFailed(output + "did not throw exception.");
    }
}

function testNaNConversion(type, name) {
  running('test storing NaN in ' + name);

  var array = new type([1, 1]);
  var results = [];

  // The code block in each of the case statements below is identical, but some
  // JavaScript engines need separate copies in order to exercise all of
  // their optimized code paths.
  try {
    switch (type) {
    case Float32Array:
      for (var i = 0; i < array.length; ++i) {
        array[i] = NaN;
        results[i] = array[i];
      }
      break;
    case Float64Array:
      for (var i = 0; i < array.length; ++i) {
        array[i] = NaN;
        results[i] = array[i];
      }
      break;
    case Int8Array:
      for (var i = 0; i < array.length; ++i) {
        array[i] = NaN;
        results[i] = array[i];
      }
      break;
    case Int16Array:
      for (var i = 0; i < array.length; ++i) {
        array[i] = NaN;
        results[i] = array[i];
      }
      break;
    case Int32Array:
      for (var i = 0; i < array.length; ++i) {
        array[i] = NaN;
        results[i] = array[i];
      }
      break;
    case Uint8Array:
      for (var i = 0; i < array.length; ++i) {
        array[i] = NaN;
        results[i] = array[i];
      }
      break;
    case Uint8ClampedArray:
      for (var i = 0; i < array.length; ++i) {
        array[i] = NaN;
        results[i] = array[i];
      }
      break;
    case Uint16Array:
      for (var i = 0; i < array.length; ++i) {
        array[i] = NaN;
        results[i] = array[i];
      }
      break;
    case Uint32Array:
      for (var i = 0; i < array.length; ++i) {
        array[i] = NaN;
        results[i] = array[i];
      }
      break;
    default:
      fail("Unhandled type");
      break;
    }

    // Some types preserve NaN values; all other types convert NaN to zero.
    if (type === Float32Array || type === Float64Array) {
      assert('initial NaN preserved', isNaN(new type([NaN])[0]));
      for (var i = 0; i < array.length; ++i)
        assert('NaN preserved via setter', isNaN(results[i]));
    } else {
      assertEq('initial NaN converted to zero', 0, new type([NaN])[0]);
      for (var i = 0; i < array.length; ++i)
        assertEq('NaN converted to zero by setter', 0, results[i]);
    }

    pass();
  } catch (e) {
      fail(e);
  }
}

//
// Test driver
//

function runTests() {
  allPassed = true;

  testSlice();
  testArrayBufferIsViewMethod();
  testInheritanceHierarchy();

  for (var i = 0; i < testCases.length; i++) {
    var testCase = testCases[i];
    running(testCase.name);
    if (!(testCase.name in window)) {
        fail("does not exist");
        continue;
    }
    var type = window[testCase.name];
    var name = testCase.name;
    if (testCase.unsigned) {
      testSetAndGet10To1(type, name);
      testConstructWithArrayOfUnsignedValues(type, name);
      testConstructWithTypedArrayOfUnsignedValues(type, name);
    } else {
      testSetAndGetPos10ToNeg10(type, name);
      testConstructWithArrayOfSignedValues(type, name);
      testConstructWithTypedArrayOfSignedValues(type, name);
    }
    if (testCase.integral) {
      testIntegralArrayTruncationBehavior(type, name, testCase.unsigned);
    }
    testGetWithOutOfRangeIndices(type, name);
    testOffsetsAndSizes(type, name, testCase.elementSizeInBytes);
    testSetFromTypedArray(type, name);
    negativeTestSetFromTypedArray(type, name);
    testSetFromArray(type, name);
    negativeTestSetFromArray(type, name);
    testSubarray(type, name);
    testSubarrayOffsetAndLengthClamping(type, name);
    negativeTestSubarray(type, name);
    testSetBoundaryConditions(type,
                              name,
                              testCase.testValues,
                              testCase.expectedValues);
    testConstructionBoundaryConditions(type,
                                       name,
                                       testCase.testValues,
                                       testCase.expectedValues);
    testConstructionWithNullBuffer(type, name);
    testConstructionWithExceptionThrowingObject(type, name);
    testConstructionWithOutOfRangeValues(type, name);
    testConstructionWithNegativeOutOfRangeValues(type, name);
    testConstructionWithUnalignedOffset(type, name, testCase.elementSizeInBytes);
    testConstructionWithUnalignedLength(type, name, testCase.elementSizeInBytes);
    testConstructionWithBothArrayBufferAndLength(type, name, testCase.elementSizeInBytes);
    testConstructionWithSubPortionOfArrayBuffer(type, name, testCase.elementSizeInBytes);
    testSubarrayWithOutOfRangeValues(type, name, testCase.elementSizeInBytes);
    testSubarrayWithDefaultValues(type, name, testCase.elementSizeInBytes);
    testSettingFromArrayWithOutOfRangeOffset(type, name);
    testSettingFromTypedArrayWithOutOfRangeOffset(type, name);
    testSettingFromArrayWithNegativeOffset(type, name);
    testSettingFromTypedArrayWithNegativeOffset(type, name);
    testSettingFromArrayWithMinusZeroOffset(type, name);
    testSettingFromTypedArrayWithMinusZeroOffset(type, name);
    testSettingFromArrayWithBoundaryOffset(type, name);
    testSettingFromTypedArrayWithBoundaryOffset(type, name);
    testSettingFromArrayWithNonIntegerOffset(type, name);
    testSettingFromTypedArrayWithNonIntegerOffset(type, name);
    testSettingFromFakeArrayWithOutOfRangeLength(type, name);
    negativeTestGetAndSetMethods(type, name);
    testNaNConversion(type, name);
  }

  printSummary();
}

runTests();
var successfullyParsed = true;

</script>
<script src="../../js/js-test-post.js"></script>

</body>
</html>
