/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.store;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;
import org.h2.mvstore.Chunk;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.WriteBuffer;
import org.h2.test.TestBase;
/**
* Test utility classes.
*/
public class TestDataUtils extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() {
testParse();
testWriteBuffer();
testEncodeLength();
testFletcher();
testMap();
testMapRandomized();
testMaxShortVarIntVarLong();
testVarIntVarLong();
testCheckValue();
testPagePos();
}
private static void testWriteBuffer() {
WriteBuffer buff = new WriteBuffer();
buff.put(new byte[1500000]);
buff.put(new byte[1900000]);
}
private void testFletcher() {
byte[] data = new byte[10000];
for (int i = 0; i < 10000; i += 1000) {
assertEquals(-1, DataUtils.getFletcher32(data, i));
}
Arrays.fill(data, (byte) 255);
for (int i = 0; i < 10000; i += 1000) {
assertEquals(-1, DataUtils.getFletcher32(data, i));
}
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 255; j++) {
Arrays.fill(data, 0, i, (byte) j);
data[i] = 0;
int a = DataUtils.getFletcher32(data, i);
if (i % 2 == 1) {
// add length: same as appending a 0
int b = DataUtils.getFletcher32(data, i + 1);
assertEquals(a, b);
}
data[i] = 10;
int c = DataUtils.getFletcher32(data, i);
assertEquals(a, c);
}
}
long last = 0;
for (int i = 1; i < 255; i++) {
Arrays.fill(data, (byte) i);
for (int j = 0; j < 10; j += 2) {
int x = DataUtils.getFletcher32(data, j);
assertTrue(x != last);
last = x;
}
}
Arrays.fill(data, (byte) 10);
assertEquals(0x1e1e1414,
DataUtils.getFletcher32(data, 10000));
assertEquals(0x1e3fa7ed,
DataUtils.getFletcher32("Fletcher32".getBytes(), 10));
}
private void testMap() {
StringBuilder buff = new StringBuilder();
DataUtils.appendMap(buff, "", "");
DataUtils.appendMap(buff, "a", "1");
DataUtils.appendMap(buff, "b", ",");
DataUtils.appendMap(buff, "c", "1,2");
DataUtils.appendMap(buff, "d", "\"test\"");
DataUtils.appendMap(buff, "e", "}");
assertEquals(":,a:1,b:\",\",c:\"1,2\",d:\"\\\"test\\\"\",e:}", buff.toString());
HashMap<String, String> m = DataUtils.parseMap(buff.toString());
assertEquals(6, m.size());
assertEquals("", m.get(""));
assertEquals("1", m.get("a"));
assertEquals(",", m.get("b"));
assertEquals("1,2", m.get("c"));
assertEquals("\"test\"", m.get("d"));
assertEquals("}", m.get("e"));
}
private void testMapRandomized() {
Random r = new Random(1);
String chars = "a_1,\\\":";
for (int i = 0; i < 1000; i++) {
StringBuilder buff = new StringBuilder();
for (int j = 0; j < 20; j++) {
buff.append(chars.charAt(r.nextInt(chars.length())));
}
try {
HashMap<String, String> map = DataUtils.parseMap(buff.toString());
assertFalse(map == null);
// ok
} catch (IllegalStateException e) {
// ok - but not another exception
}
}
}
private void testMaxShortVarIntVarLong() {
ByteBuffer buff = ByteBuffer.allocate(100);
DataUtils.writeVarInt(buff, DataUtils.COMPRESSED_VAR_INT_MAX);
assertEquals(3, buff.position());
buff.rewind();
DataUtils.writeVarInt(buff, DataUtils.COMPRESSED_VAR_INT_MAX + 1);
assertEquals(4, buff.position());
buff.rewind();
DataUtils.writeVarLong(buff, DataUtils.COMPRESSED_VAR_LONG_MAX);
assertEquals(7, buff.position());
buff.rewind();
DataUtils.writeVarLong(buff, DataUtils.COMPRESSED_VAR_LONG_MAX + 1);
assertEquals(8, buff.position());
buff.rewind();
}
private void testVarIntVarLong() {
ByteBuffer buff = ByteBuffer.allocate(100);
for (long x = 0; x < 1000; x++) {
testVarIntVarLong(buff, x);
testVarIntVarLong(buff, -x);
}
for (long x = Long.MIN_VALUE, i = 0; i < 1000; x++, i++) {
testVarIntVarLong(buff, x);
}
for (long x = Long.MAX_VALUE, i = 0; i < 1000; x--, i++) {
testVarIntVarLong(buff, x);
}
for (int shift = 0; shift < 64; shift++) {
for (long x = 250; x < 260; x++) {
testVarIntVarLong(buff, x << shift);
testVarIntVarLong(buff, -(x << shift));
}
}
// invalid varInt / varLong
// should work, but not read far too much
for (int i = 0; i < 50; i++) {
buff.put((byte) 255);
}
buff.flip();
assertEquals(-1, DataUtils.readVarInt(buff));
assertEquals(5, buff.position());
buff.rewind();
assertEquals(-1, DataUtils.readVarLong(buff));
assertEquals(10, buff.position());
buff.clear();
testVarIntVarLong(buff, DataUtils.COMPRESSED_VAR_INT_MAX);
testVarIntVarLong(buff, DataUtils.COMPRESSED_VAR_INT_MAX + 1);
testVarIntVarLong(buff, DataUtils.COMPRESSED_VAR_LONG_MAX);
testVarIntVarLong(buff, DataUtils.COMPRESSED_VAR_LONG_MAX + 1);
}
private void testVarIntVarLong(ByteBuffer buff, long x) {
int len;
byte[] data;
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataUtils.writeVarLong(out, x);
data = out.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
DataUtils.writeVarLong(buff, x);
len = buff.position();
assertEquals(data.length, len);
byte[] data2 = new byte[len];
buff.position(0);
buff.get(data2);
assertEquals(data2, data);
buff.flip();
long y = DataUtils.readVarLong(buff);
assertEquals(y, x);
assertEquals(len, buff.position());
assertEquals(len, DataUtils.getVarLongLen(x));
buff.clear();
int intX = (int) x;
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataUtils.writeVarInt(out, intX);
data = out.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
DataUtils.writeVarInt(buff, intX);
len = buff.position();
assertEquals(data.length, len);
data2 = new byte[len];
buff.position(0);
buff.get(data2);
assertEquals(data2, data);
buff.flip();
int intY = DataUtils.readVarInt(buff);
assertEquals(intY, intX);
assertEquals(len, buff.position());
assertEquals(len, DataUtils.getVarIntLen(intX));
buff.clear();
}
private void testCheckValue() {
// 0 xor 0 = 0
assertEquals(0, DataUtils.getCheckValue(0));
// 1111... xor 1111... = 0
assertEquals(0, DataUtils.getCheckValue(-1));
// 0 xor 1111... = 1111...
assertEquals((short) -1, DataUtils.getCheckValue(-1 >>> 16));
// 1111... xor 0 = 1111...
assertEquals((short) -1, DataUtils.getCheckValue(-1 << 16));
// 0 xor 1000... = 1000...
assertEquals((short) (1 << 15), DataUtils.getCheckValue(1 << 15));
// 1000... xor 0 = 1000...
assertEquals((short) (1 << 15), DataUtils.getCheckValue(1 << 31));
}
private void testParse() {
for (long i = -1; i != 0; i >>>= 1) {
String x = Long.toHexString(i);
assertEquals(i, DataUtils.parseHexLong(x));
x = Long.toHexString(-i);
assertEquals(-i, DataUtils.parseHexLong(x));
int j = (int) i;
x = Integer.toHexString(j);
assertEquals(j, DataUtils.parseHexInt(x));
j = (int) -i;
x = Integer.toHexString(j);
assertEquals(j, DataUtils.parseHexInt(x));
}
}
private void testPagePos() {
assertEquals(0, DataUtils.PAGE_TYPE_LEAF);
assertEquals(1, DataUtils.PAGE_TYPE_NODE);
long max = DataUtils.getPagePos(Chunk.MAX_ID, Integer.MAX_VALUE,
Integer.MAX_VALUE, DataUtils.PAGE_TYPE_NODE);
String hex = Long.toHexString(max);
assertEquals(max, DataUtils.parseHexLong(hex));
assertEquals(Chunk.MAX_ID, DataUtils.getPageChunkId(max));
assertEquals(Integer.MAX_VALUE, DataUtils.getPageOffset(max));
assertEquals(DataUtils.PAGE_LARGE, DataUtils.getPageMaxLength(max));
assertEquals(DataUtils.PAGE_TYPE_NODE, DataUtils.getPageType(max));
long overflow = DataUtils.getPagePos(Chunk.MAX_ID + 1,
Integer.MAX_VALUE, Integer.MAX_VALUE, DataUtils.PAGE_TYPE_NODE);
assertTrue(Chunk.MAX_ID + 1 != DataUtils.getPageChunkId(overflow));
for (int i = 0; i < Chunk.MAX_ID; i++) {
long pos = DataUtils.getPagePos(i, 3, 128, 1);
assertEquals(i, DataUtils.getPageChunkId(pos));
assertEquals(3, DataUtils.getPageOffset(pos));
assertEquals(128, DataUtils.getPageMaxLength(pos));
assertEquals(1, DataUtils.getPageType(pos));
}
for (int type = 0; type <= 1; type++) {
for (int chunkId = 0; chunkId < Chunk.MAX_ID;
chunkId += Chunk.MAX_ID / 100) {
for (long offset = 0; offset < Integer.MAX_VALUE;
offset += Integer.MAX_VALUE / 100) {
for (int length = 0; length < 2000000; length += 200000) {
long pos = DataUtils.getPagePos(
chunkId, (int) offset, length, type);
assertEquals(chunkId, DataUtils.getPageChunkId(pos));
assertEquals(offset, DataUtils.getPageOffset(pos));
assertTrue(DataUtils.getPageMaxLength(pos) >= length);
assertTrue(DataUtils.getPageType(pos) == type);
}
}
}
}
}
private void testEncodeLength() {
int lastCode = 0;
assertEquals(0, DataUtils.encodeLength(32));
assertEquals(1, DataUtils.encodeLength(33));
assertEquals(1, DataUtils.encodeLength(48));
assertEquals(2, DataUtils.encodeLength(49));
assertEquals(2, DataUtils.encodeLength(64));
assertEquals(3, DataUtils.encodeLength(65));
assertEquals(30, DataUtils.encodeLength(1024 * 1024));
assertEquals(31, DataUtils.encodeLength(1024 * 1024 + 1));
assertEquals(31, DataUtils.encodeLength(Integer.MAX_VALUE));
int[] maxLengthForIndex = {32, 48, 64, 96, 128, 192, 256,
384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144,
8192, 12288, 16384, 24576, 32768, 49152, 65536,
98304, 131072, 196608, 262144, 393216, 524288,
786432, 1048576};
for (int i = 0; i < maxLengthForIndex.length; i++) {
assertEquals(i, DataUtils.encodeLength(maxLengthForIndex[i]));
assertEquals(i + 1, DataUtils.encodeLength(maxLengthForIndex[i] + 1));
}
for (int i = 1024 * 1024 + 1; i < 100 * 1024 * 1024; i += 1024) {
int code = DataUtils.encodeLength(i);
assertEquals(31, code);
}
for (int i = 0; i < 2 * 1024 * 1024; i++) {
int code = DataUtils.encodeLength(i);
assertTrue(code <= 31 && code >= 0);
assertTrue(code >= lastCode);
if (code > lastCode) {
lastCode = code;
}
int max = DataUtils.getPageMaxLength(code << 1);
assertTrue(max >= i && max >= 32);
}
}
}