blob: ab36559f1f700139c0b86940277a012dc0dc844d [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@TestOn('!vm')
import 'dart:typed_data';
import 'package:test/test.dart';
import 'package:typed_data/src/typed_buffer.dart';
const List<int> browserSafeIntSamples = [
0x8000000000000000, // 2^63
0x100000001,
0x100000000, // 2^32
0x0ffffffff,
0xaaaaaaaa,
0x80000001,
0x80000000, // 2^31
0x7fffffff,
0x55555555,
0x10001,
0x10000, // 2^16
0x0ffff,
0xaaaa,
0x8001,
0x8000, // 2^15
0x7fff,
0x5555,
0x101,
0x100, // 2^8
0x0ff,
0xaa,
0x81,
0x80, // 2^7
0x7f,
0x55,
0x02,
0x01,
0x00
];
void main() {
initTests(browserSafeIntSamples);
}
void initTests(List<int> intSamples) {
testUint(intSamples, 8, (l) => Uint8Buffer(l));
testInt(intSamples, 8, (l) => Int8Buffer(l));
test('Uint8ClampedBuffer', () {
testIntBuffer(
intSamples, 8, 0, 255, (l) => Uint8ClampedBuffer(l), clampUint8);
});
testUint(intSamples, 16, (l) => Uint16Buffer(l));
testInt(intSamples, 16, (l) => Int16Buffer(l));
testUint(intSamples, 32, (l) => Uint32Buffer(l));
testInt(intSamples, 32, (l) => Int32Buffer(l));
testUint(intSamples, 64, (l) => Uint64Buffer(l),
// JS doesn't support 64-bit ints, so only test this on the VM.
testOn: 'dart-vm');
testInt(intSamples, 64, (l) => Int64Buffer(l),
// JS doesn't support 64-bit ints, so only test this on the VM.
testOn: 'dart-vm');
testInt32x4Buffer(intSamples);
var roundedFloatSamples = floatSamples.map(roundToFloat).toList();
testFloatBuffer(32, roundedFloatSamples, () => Float32Buffer(), roundToFloat);
testFloatBuffer(64, doubleSamples, () => Float64Buffer(), (x) => x);
testFloat32x4Buffer(roundedFloatSamples);
group('addAll', () {
for (var type in ['a list', 'an iterable']) {
group('with $type', () {
Iterable<int> source;
Uint8Buffer buffer;
setUp(() {
source = [1, 2, 3, 4, 5];
if (type == 'an iterable') {
source = (source as List<int>).reversed.toList().reversed;
}
buffer = Uint8Buffer();
});
test('adds values to the buffer', () {
buffer.addAll(source, 1, 4);
expect(buffer, equals([2, 3, 4]));
buffer.addAll(source, 4);
expect(buffer, equals([2, 3, 4, 5]));
buffer.addAll(source, 0, 1);
expect(buffer, equals([2, 3, 4, 5, 1]));
});
test('does nothing for empty slices', () {
buffer.addAll([6, 7, 8, 9, 10]);
buffer.addAll(source, 0, 0);
expect(buffer, equals([6, 7, 8, 9, 10]));
buffer.addAll(source, 3, 3);
expect(buffer, equals([6, 7, 8, 9, 10]));
buffer.addAll(source, 5);
expect(buffer, equals([6, 7, 8, 9, 10]));
buffer.addAll(source, 5, 5);
expect(buffer, equals([6, 7, 8, 9, 10]));
});
test('throws errors for invalid start and end', () {
expect(() => buffer.addAll(source, -1), throwsRangeError);
expect(() => buffer.addAll(source, -1, 2), throwsRangeError);
expect(() => buffer.addAll(source, 10), throwsStateError);
expect(() => buffer.addAll(source, 10, 11), throwsStateError);
expect(() => buffer.addAll(source, 3, 2), throwsRangeError);
expect(() => buffer.addAll(source, 3, 10), throwsStateError);
expect(() => buffer.addAll(source, 3, -1), throwsRangeError);
});
});
}
});
group('insertAll', () {
for (var type in ['a list', 'an iterable']) {
group('with $type', () {
Iterable<int> source;
Uint8Buffer buffer;
setUp(() {
source = [1, 2, 3, 4, 5];
if (type == 'an iterable') {
source = (source as List<int>).reversed.toList().reversed;
}
buffer = Uint8Buffer()..addAll([6, 7, 8, 9, 10]);
});
test('inserts values into the buffer', () {
buffer.insertAll(0, source, 1, 4);
expect(buffer, equals([2, 3, 4, 6, 7, 8, 9, 10]));
buffer.insertAll(3, source, 4);
expect(buffer, equals([2, 3, 4, 5, 6, 7, 8, 9, 10]));
buffer.insertAll(5, source, 0, 1);
expect(buffer, equals([2, 3, 4, 5, 6, 1, 7, 8, 9, 10]));
});
// Regression test for #1.
test('inserts values into the buffer after removeRange()', () {
buffer.removeRange(1, 4);
buffer.insertAll(1, source);
expect(buffer, equals([6, 1, 2, 3, 4, 5, 10]));
});
test('does nothing for empty slices', () {
buffer.insertAll(1, source, 0, 0);
expect(buffer, equals([6, 7, 8, 9, 10]));
buffer.insertAll(2, source, 3, 3);
expect(buffer, equals([6, 7, 8, 9, 10]));
buffer.insertAll(3, source, 5);
expect(buffer, equals([6, 7, 8, 9, 10]));
buffer.insertAll(4, source, 5, 5);
expect(buffer, equals([6, 7, 8, 9, 10]));
});
test('throws errors for invalid start and end', () {
expect(() => buffer.insertAll(-1, source), throwsRangeError);
expect(() => buffer.insertAll(6, source), throwsRangeError);
expect(() => buffer.insertAll(1, source, -1), throwsRangeError);
expect(() => buffer.insertAll(2, source, -1, 2), throwsRangeError);
expect(() => buffer.insertAll(3, source, 10), throwsStateError);
expect(() => buffer.insertAll(4, source, 10, 11), throwsStateError);
expect(() => buffer.insertAll(5, source, 3, 2), throwsRangeError);
expect(() => buffer.insertAll(1, source, 3, 10), throwsStateError);
expect(() => buffer.insertAll(2, source, 3, -1), throwsRangeError);
});
});
}
});
}
const doubleSamples = [
0.0,
5e-324, // Minimal denormal value.
2.225073858507201e-308, // Maximal denormal value.
2.2250738585072014e-308, // Minimal normal value.
0.9999999999999999, // Maximum value < 1.
1.0,
1.0000000000000002, // Minimum value > 1.
4294967295.0, // 2^32 -1.
4294967296.0, // 2^32.
4503599627370495.5, // Maximal fractional value.
9007199254740992.0, // Maximal exact value (adding one gets lost).
1.7976931348623157e+308, // Maximal value.
1.0 / 0.0, // Infinity.
0.0 / 0.0, // NaN.
0.49999999999999994, // Round-traps 1-3 (adding 0.5 and rounding towards
4503599627370497.0, // minus infinity will not be the same as rounding
9007199254740991.0 // to nearest with 0.5 rounding up).
];
const floatSamples = [
0.0,
1.4e-45, // Minimal denormal value.
1.1754942E-38, // Maximal denormal value.
1.17549435E-38, // Minimal normal value.
0.99999994, // Maximal value < 1.
1.0,
1.0000001, // Minimal value > 1.
8388607.5, // Maximal fractional value.
16777216.0, // Maximal exact value.
3.4028235e+38, // Maximal value.
1.0 / 0.0, // Infinity.
0.0 / 0.0, // NaN.
0.99999994, // Round traps 1-3.
8388609.0,
16777215.0
];
int clampUint8(x) => x < 0 ? 0 : x > 255 ? 255 : x;
void doubleEqual(x, y) {
if (y.isNaN) {
expect(x.isNaN, isTrue);
} else {
expect(x, equals(y));
}
}
Rounder intRounder(bits) {
var highBit = 1 << (bits - 1);
var mask = highBit - 1;
return (int x) => (x & mask) - (x & highBit);
}
double roundToFloat(double value) {
return (Float32List(1)..[0] = value)[0];
}
void testFloat32x4Buffer(List floatSamples) {
var float4Samples = <Float32x4>[];
for (var i = 0; i < floatSamples.length - 3; i++) {
float4Samples.add(Float32x4(floatSamples[i], floatSamples[i + 1],
floatSamples[i + 2], floatSamples[i + 3]));
}
void floatEquals(x, y) {
if (y.isNaN) {
expect(x.isNaN, isTrue);
} else {
expect(x, equals(y));
}
}
void x4Equals(Float32x4 x, Float32x4 y) {
floatEquals(x.x, y.x);
floatEquals(x.y, y.y);
floatEquals(x.z, y.z);
floatEquals(x.w, y.w);
}
test('Float32x4Buffer', () {
var buffer = Float32x4Buffer(5);
expect(buffer, const TypeMatcher<List<Float32x4>>());
expect(buffer.length, equals(5));
expect(buffer.elementSizeInBytes, equals(128 ~/ 8));
expect(buffer.lengthInBytes, equals(5 * 128 ~/ 8));
expect(buffer.offsetInBytes, equals(0));
x4Equals(buffer[0], Float32x4.zero());
buffer.length = 0;
expect(buffer.length, equals(0));
for (var sample in float4Samples) {
buffer.add(sample);
x4Equals(buffer[buffer.length - 1], sample);
}
expect(buffer.length, equals(float4Samples.length));
buffer.addAll(float4Samples);
expect(buffer.length, equals(float4Samples.length * 2));
for (var i = 0; i < float4Samples.length; i++) {
x4Equals(buffer[i], buffer[float4Samples.length + i]);
}
buffer.removeRange(4, 4 + float4Samples.length);
for (var i = 0; i < float4Samples.length; i++) {
x4Equals(buffer[i], float4Samples[i]);
}
// Test underlying buffer.
buffer.length = 1;
buffer[0] = float4Samples[0]; // Does not contain NaN.
var floats = Float32List.view(buffer.buffer);
expect(floats[0], equals(buffer[0].x));
expect(floats[1], equals(buffer[0].y));
expect(floats[2], equals(buffer[0].z));
expect(floats[3], equals(buffer[0].w));
});
}
// Takes bit-size, min value, max value, function to create a buffer, and
// the rounding that is applied when storing values outside the valid range
// into the buffer.
void testFloatBuffer(
int bitSize,
List<double> samples,
TypedDataBuffer<double> Function() create,
double Function(double v) round,
) {
test('Float${bitSize}Buffer', () {
var buffer = create();
expect(buffer, const TypeMatcher<List<double>>());
var byteSize = bitSize ~/ 8;
expect(buffer.length, equals(0));
buffer.add(0.0);
expect(buffer.length, equals(1));
expect(buffer.removeLast(), equals(0.0));
expect(buffer.length, equals(0));
for (var value in samples) {
buffer.add(value);
doubleEqual(buffer[buffer.length - 1], round(value));
}
expect(buffer.length, equals(samples.length));
buffer.addAll(samples);
expect(buffer.length, equals(samples.length * 2));
for (var i = 0; i < samples.length; i++) {
doubleEqual(buffer[i], buffer[samples.length + i]);
}
buffer.removeRange(samples.length, buffer.length);
expect(buffer.length, equals(samples.length));
buffer.insertAll(0, samples);
expect(buffer.length, equals(samples.length * 2));
for (var i = 0; i < samples.length; i++) {
doubleEqual(buffer[i], buffer[samples.length + i]);
}
buffer.length = samples.length;
expect(buffer.length, equals(samples.length));
// TypedData.
expect(buffer.elementSizeInBytes, equals(byteSize));
expect(buffer.lengthInBytes, equals(byteSize * buffer.length));
expect(buffer.offsetInBytes, equals(0));
// Accessing the buffer works.
// Accessing the underlying buffer works.
buffer.length = 2;
buffer[0] = samples[0];
buffer[1] = samples[1];
var bytes = Uint8List.view(buffer.buffer);
for (var i = 0; i < byteSize; i++) {
var tmp = bytes[i];
bytes[i] = bytes[byteSize + i];
bytes[byteSize + i] = tmp;
}
doubleEqual(buffer[0], round(samples[1]));
doubleEqual(buffer[1], round(samples[0]));
});
}
void testInt(
List<int> intSamples,
int bits,
TypedDataBuffer<int> Function(int length) buffer, {
String testOn,
}) {
var min = -(1 << (bits - 1));
var max = -(min + 1);
test('Int${bits}Buffer', () {
testIntBuffer(intSamples, bits, min, max, buffer, intRounder(bits));
}, testOn: testOn);
}
void testInt32x4Buffer(List<int> intSamples) {
test('Int32x4Buffer', () {
var bytes = 128 ~/ 8;
Matcher equals32x4(Int32x4 expected) => MatchesInt32x4(expected);
var buffer = Int32x4Buffer(0);
expect(buffer, const TypeMatcher<List<Int32x4>>());
expect(buffer.length, equals(0));
expect(buffer.elementSizeInBytes, equals(bytes));
expect(buffer.lengthInBytes, equals(0));
expect(buffer.offsetInBytes, equals(0));
var sample = Int32x4(-0x80000000, -1, 0, 0x7fffffff);
buffer.add(sample);
expect(buffer.length, equals(1));
expect(buffer[0], equals32x4(sample));
expect(buffer.elementSizeInBytes, equals(bytes));
expect(buffer.lengthInBytes, equals(bytes));
expect(buffer.offsetInBytes, equals(0));
buffer.length = 0;
expect(buffer.length, equals(0));
var samples = intSamples
.map((value) => Int32x4(value, -value, ~value, ~ -value))
.toList();
for (var value in samples) {
var length = buffer.length;
buffer.add(value);
expect(buffer.length, equals(length + 1));
expect(buffer[length], equals32x4(value));
}
buffer.addAll(samples); // Add all the values at once.
for (var i = 0; i < samples.length; i++) {
expect(buffer[samples.length + i], equals32x4(buffer[i]));
}
// Remove range works and changes length.
buffer.removeRange(samples.length, buffer.length);
expect(buffer.length, equals(samples.length));
// Accessing the underlying buffer works.
buffer.length = 2;
buffer[0] = Int32x4(-80000000, 0x7fffffff, 0, -1);
var byteBuffer = Uint8List.view(buffer.buffer);
var halfBytes = bytes ~/ 2;
for (var i = 0; i < halfBytes; i++) {
var tmp = byteBuffer[i];
byteBuffer[i] = byteBuffer[halfBytes + i];
byteBuffer[halfBytes + i] = tmp;
}
var result = Int32x4(0, -1, -80000000, 0x7fffffff);
expect(buffer[0], equals32x4(result));
});
}
void testIntBuffer(
List<int> intSamples,
int bits,
int min,
int max,
TypedDataBuffer<int> Function(int length) create,
int Function(int val) round,
) {
assert(round(min) == min);
assert(round(max) == max);
// All int buffers default to the value 0.
var buffer = create(0);
expect(buffer, const TypeMatcher<List<int>>());
expect(buffer.length, equals(0));
var bytes = bits ~/ 8;
expect(buffer.elementSizeInBytes, equals(bytes));
expect(buffer.lengthInBytes, equals(0));
expect(buffer.offsetInBytes, equals(0));
buffer.add(min);
expect(buffer.length, equals(1));
expect(buffer[0], equals(min));
expect(buffer.elementSizeInBytes, equals(bytes));
expect(buffer.lengthInBytes, equals(bytes));
expect(buffer.offsetInBytes, equals(0));
buffer.length = 0;
expect(buffer.length, equals(0));
var samples = intSamples.toList()..addAll(intSamples.map((x) => -x));
for (var value in samples) {
var length = buffer.length;
buffer.add(value);
expect(buffer.length, equals(length + 1));
expect(buffer[length], equals(round(value)));
}
buffer.addAll(samples); // Add all the values at once.
for (var i = 0; i < samples.length; i++) {
expect(buffer[samples.length + i], equals(buffer[i]));
}
// Remove range works and changes length.
buffer.removeRange(samples.length, buffer.length);
expect(buffer.length, equals(samples.length));
// Both values are in `samples`, but equality is performed without rounding.
// For signed 64 bit ints, min and max wrap around, min-1=max and max+1=min
if (bits == 64) {
// TODO(keertip): fix tests for Uint64 / Int64 as now Uints are represented
// as signed ints.
expect(buffer.contains(min - 1), isTrue);
expect(buffer.contains(max + 1), isTrue);
} else {
// Both values are in `samples`, but equality is performed without rounding.
expect(buffer.contains(min - 1), isFalse);
expect(buffer.contains(max + 1), isFalse);
}
expect(buffer.contains(round(min - 1)), isTrue);
expect(buffer.contains(round(max + 1)), isTrue);
// Accessing the underlying buffer works.
buffer.length = 2;
buffer[0] = min;
buffer[1] = max;
var byteBuffer = Uint8List.view(buffer.buffer);
var byteSize = buffer.elementSizeInBytes;
for (var i = 0; i < byteSize; i++) {
var tmp = byteBuffer[i];
byteBuffer[i] = byteBuffer[byteSize + i];
byteBuffer[byteSize + i] = tmp;
}
expect(buffer[0], equals(max));
expect(buffer[1], equals(min));
}
void testUint(
List<int> intSamples,
int bits,
TypedDataBuffer<int> Function(int length) buffer, {
String testOn,
}) {
var min = 0;
var rounder = uintRounder(bits);
var max = rounder(-1);
test('Uint${bits}Buffer', () {
testIntBuffer(intSamples, bits, min, max, buffer, rounder);
}, testOn: testOn);
}
Rounder uintRounder(bits) {
var halfbits = (1 << (bits ~/ 2)) - 1;
var mask = halfbits | (halfbits << (bits ~/ 2));
return (int x) => x & mask;
}
typedef Rounder = int Function(int value);
class MatchesInt32x4 extends Matcher {
Int32x4 result;
MatchesInt32x4(this.result);
Description describe(Description description) =>
description.add('Int32x4.==');
bool matches(item, Map matchState) {
if (item is! Int32x4) return false;
Int32x4 value = item;
return result.x == value.x &&
result.y == value.y &&
result.z == value.z &&
result.w == value.w;
}
}