package com.peterhi.runtime;
import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.junit.Before;
import org.junit.Test;
import com.peterhi.runtime.BitStream;
import com.peterhi.runtime.InsufficientBufferException;
public class BitStreamTest {
private SortedMap<BigInteger, Integer> numbers;
@Before
public void setUp() {
int[] bits = new int[] { 1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128 };
numbers = new TreeMap<BigInteger, Integer>();
for (int bit : bits) {
BigInteger ref = BigInteger.valueOf(2).pow(bit).divide(BigInteger.valueOf(2));
BigInteger min = ref.negate();
BigInteger max = ref.subtract(BigInteger.ONE);
BigInteger under = min.subtract(BigInteger.ONE);
BigInteger over = max.add(BigInteger.ONE);
numbers.put(min, bit);
numbers.put(max, bit);
numbers.put(under, bit);
numbers.put(over, bit);
}
}
@Test
public void testConstructorCapacity() throws Exception {
{
BitStream bs = new BitStream(32);
assertEquals(0, bs.available());
assertEquals(32, bs.bytes());
}
try {
BitStream bs = new BitStream(-1);
fail();
} catch (IllegalArgumentException ex) {
}
}
@Test
public void testConstructorBufOffLen() throws Exception {
{
BitStream bs = new BitStream(new byte[32], Byte.SIZE * 16, Byte.SIZE * 16);
assertEquals(Byte.SIZE * 16, bs.available());
assertEquals(16, bs.bytes());
}
try {
BitStream bs = new BitStream(null, 0, 1);
fail();
} catch (NullPointerException ex) {
}
try {
BitStream bs = new BitStream(new byte[1], -1, 1);
fail();
} catch (IndexOutOfBoundsException ex) {
}
try {
BitStream bs = new BitStream(new byte[1], 1, -1);
fail();
} catch (IndexOutOfBoundsException ex) {
}
try {
BitStream bs = new BitStream(new byte[1], 1, Byte.SIZE);
fail();
} catch (IndexOutOfBoundsException ex) {
}
}
@Test
public void testAvailable() throws Exception {
{
BitStream bs = new BitStream(32);
assertEquals(0, bs.available());
assertEquals(32, bs.bytes());
}
{
byte[] data = new byte[32];
BitStream bs = new BitStream(data, 0, Byte.SIZE * data.length);
assertEquals(Byte.SIZE * data.length, bs.available());
assertEquals(data.length, bs.bytes());
}
{
BitStream bs = new BitStream(32);
bs.writeBit(1);
assertEquals(1, bs.available());
assertEquals(32, bs.bytes());
}
}
@Test
public void testSize() throws Exception {
{
BitStream bs = new BitStream(32);
assertEquals(0, bs.available());
assertEquals(32, bs.bytes());
}
{
byte[] data = new byte[32];
BitStream bs = new BitStream(data, Byte.SIZE * 16, Byte.SIZE * 16);
assertEquals(Byte.SIZE * 16, bs.available());
assertEquals(16, bs.bytes());
}
{
BitStream bs = new BitStream(1);
for (int i = 0; i < 9; i++) {
bs.writeBit(1);
}
assertEquals(9, bs.available());
assertEquals(129, bs.bytes());
}
}
@Test
public void testReadBit() throws Exception {
{
byte[] data = new byte[] { (byte )170, (byte )85 };
BitStream bs = new BitStream(data, 0, Byte.SIZE * data.length);
for (int i = 0; i < Byte.SIZE; i++) {
int bit = bs.readBit();
if (i % 2 == 0) {
assertEquals(0, bit);
} else {
assertEquals(1, bit);
}
}
for (int i = 0; i < Byte.SIZE; i++) {
int bit = bs.readBit();
if (i % 2 == 0) {
assertEquals(1, bit);
} else {
assertEquals(0, bit);
}
}
assertEquals(0, bs.available());
assertEquals(2, bs.bytes());
}
{
BitStream bs = new BitStream(32);
assertEquals(0, bs.available());
assertEquals(32, bs.bytes());
assertEquals(-1, bs.readBit());
}
}
@Test
public void testWriteBit() throws Exception {
{
BitStream bs = new BitStream(1);
for (int i = 0; i < 10; i++) {
bs.writeBit(i % 2);
}
assertEquals(10, bs.available());
assertEquals(129, bs.bytes());
for (int i = 0; i < 10; i++) {
assertEquals(i % 2, bs.readBit());
}
assertEquals(0, bs.available());
assertEquals(129, bs.bytes());
assertEquals(-1, bs.readBit());
}
}
@Test
public void testReadBits() throws Exception {
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
int bytesWritten = 0;
for (Map.Entry<BigInteger, Integer> entry : numbers.entrySet()) {
BigInteger number = entry.getKey();
Integer size = entry.getValue();
if (size == Byte.SIZE) {
dos.writeByte(number.byteValue());
bytesWritten += Byte.SIZE / 8;
} else if (size == Short.SIZE) {
dos.writeShort(number.shortValue());
bytesWritten += Short.SIZE / 8;
} else if (size == Integer.SIZE) {
dos.writeInt(number.intValue());
bytesWritten += Integer.SIZE / 8;
} else if (size == Long.SIZE) {
dos.writeLong(number.longValue());
bytesWritten += Long.SIZE / 8;
}
}
byte[] data = baos.toByteArray();
BitStream bs = new BitStream(data, 0, Byte.SIZE * data.length);
for (Map.Entry<BigInteger, Integer> entry : numbers.entrySet()) {
BigInteger number = entry.getKey();
Integer size = entry.getValue();
if (size == Byte.SIZE) {
assertEquals(number.byteValue(), bs.readBits(size, true).byteValue());
} else if (size == Short.SIZE) {
assertEquals(number.shortValue(), bs.readBits(size, true).shortValue());
} else if (size == Integer.SIZE) {
assertEquals(number.intValue(), bs.readBits(size, true).intValue());
} else if (size == Long.SIZE) {
assertEquals(number.longValue(), bs.readBits(size, true).longValue());
}
}
assertEquals(0, bs.available());
assertEquals(bytesWritten, bs.bytes());
}
try {
BitStream bs = new BitStream(32);
bs.readBits(-1, true);
fail();
} catch (IllegalArgumentException ex) {
}
try {
BitStream bs = new BitStream(32);
bs.readBits(1, true);
fail();
} catch (EOFException ex) {
}
try {
BitStream bs = new BitStream(32);
bs.writeBit(1);
bs.readBits(2, true);
fail();
} catch (InsufficientBufferException ex) {
}
}
@Test
public void testWriteBits() throws Exception {
{
BitStream bs = new BitStream(60);
for (Map.Entry<BigInteger, Integer> entry : numbers.entrySet()) {
BigInteger number = entry.getKey();
Integer size = entry.getValue();
if (size == Byte.SIZE || size == Short.SIZE || size == Integer.SIZE || size == Long.SIZE) {
bs.writeBits(number, size);
}
}
assertEquals(Byte.SIZE * 60, bs.available());
assertEquals(60, bs.bytes());
byte[] data = bs.toByteArray();
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
for (Map.Entry<BigInteger, Integer> entry : numbers.entrySet()) {
BigInteger number = entry.getKey();
Integer size = entry.getValue();
if (size == Byte.SIZE) {
assertEquals(number.byteValue(), dis.readByte());
} else if (size == Short.SIZE) {
assertEquals(number.shortValue(), dis.readShort());
} else if (size == Integer.SIZE) {
assertEquals(number.intValue(), dis.readInt());
} else if (size == Long.SIZE) {
assertEquals(number.longValue(), dis.readLong());
}
}
}
try {
BitStream bs = new BitStream(32);
bs.writeBits(null, 0);
fail();
} catch (NullPointerException ex) {
}
try {
BitStream bs = new BitStream(32);
bs.writeBits(BigInteger.ONE, -1);
fail();
} catch (IllegalArgumentException ex) {
}
}
@Test
public void testReadAndWriteArbitraryBits() throws Exception {
// ceil(1095 * 4 / 8)
BitStream bsw = new BitStream(548);
int bitsWritten = 0;
for (Map.Entry<BigInteger, Integer> entry : numbers.entrySet()) {
BigInteger number = entry.getKey();
Integer size = entry.getValue();
bsw.writeBits(number, size);
bitsWritten += size;
}
assertEquals(bitsWritten, bsw.available());
assertEquals(548, bsw.bytes());
byte[] data = bsw.toByteArray();
BitStream bsr = new BitStream(data, 0, bitsWritten);
for (Map.Entry<BigInteger, Integer> entry : numbers.entrySet()) {
BigInteger number = entry.getKey();
Integer size = entry.getValue();
BigInteger value = bsr.readBits(size, true);
BigInteger mask = BigInteger.ZERO;
for (int i = 0; i < size; i++) {
mask = mask.or(BigInteger.ONE.shiftLeft(i));
}
assertEquals(number.and(mask), value.and(mask));
}
assertEquals(0, bsr.available());
assertEquals(548, bsr.bytes());
}
@Test
public void testReadMarks() throws Exception {
BitStream bs = new BitStream(new byte[] { 1, 2 }, 0, Byte.SIZE * 2);
assertEquals(1, bs.readBits(8, true).byteValue());
bs.setReadMark();
assertEquals(2, bs.readBits(8, true).byteValue());
assertEquals(-1, bs.readBit());
bs.goToReadMark();
assertEquals(2, bs.readBits(8, true).byteValue());
assertEquals(-1, bs.readBit());
bs.clearReadMark();
assertEquals(-1, bs.readBit());
assertEquals(0, bs.available());
assertEquals(2, bs.bytes());
bs.goToReadFromBeginning();
assertEquals(1, bs.readBits(8, true).byteValue());
assertEquals(2, bs.readBits(8, true).byteValue());
}
@Test
public void testWriteMarks() throws Exception {
BitStream bs = new BitStream(2);
bs.writeBits(BigInteger.ONE, 8);
bs.setWriteMark();
bs.writeBits(BigInteger.TEN, 8);
assertEquals(Byte.SIZE * 2, bs.available());
assertEquals(2, bs.bytes());
byte[] data = bs.toByteArray();
assertEquals(2, data.length);
assertEquals(1, data[0]);
assertEquals(10, data[1]);
bs.goToWriteMark();
bs.writeBits(BigInteger.ZERO, 8);
assertEquals(Byte.SIZE * 2, bs.available());
assertEquals(2, bs.bytes());
data = bs.toByteArray();
assertEquals(2, data.length);
assertEquals(1, data[0]);
assertEquals(0, data[1]);
bs.clearWriteMark();
bs.writeBits(BigInteger.TEN, 8);
data = bs.toByteArray();
assertEquals(3, data.length);
assertEquals(Byte.SIZE * 3, bs.available());
assertEquals(130, bs.bytes());
bs.goToWriteFromBeginning();
bs.writeBits(BigInteger.ZERO, 8);
bs.writeBits(BigInteger.ZERO, 8);
data = bs.toByteArray();
assertEquals(0, data[0]);
assertEquals(0, data[1]);
}
@Test
public void testAlignWrite() throws Exception {
BitStream bs = new BitStream(2);
bs.writeBit(1);
bs.alignWrite();
bs.writeBit(1);
byte[] data = bs.toByteArray();
assertEquals(1, data[0]);
assertEquals(1, data[1]);
assertEquals(9, bs.available());
assertEquals(2, bs.bytes());
}
@Test
public void testAlignRead() throws Exception {
BitStream bs = new BitStream(new byte[] { 1, 1 }, 0, Byte.SIZE * 2);
assertEquals(1, bs.readBit());
bs.alignRead();
assertEquals(1, bs.readBit());
assertEquals(7, bs.available());
assertEquals(2, bs.bytes());
}
@Test
public void testToByteArray() throws Exception {
BitStream bs = new BitStream(2);
bs.writeBits(BigInteger.valueOf(255), 8);
bs.writeBits(BigInteger.valueOf(255), 8);
byte[] data = bs.toByteArray();
assertEquals(-1, data[0]);
assertEquals(-1, data[1]);
bs.readBits(4, true);
data = bs.toByteArray();
assertEquals(-1, data[0]);
assertEquals(15, data[1]);
assertEquals(12, bs.available());
assertEquals(2, bs.bytes());
}
@Test
public void testClear() throws Exception {
BitStream bs = new BitStream(2);
bs.writeBit(1);
bs.alignRead();
bs.writeBit(0);
bs.writeBit(1);
assertEquals(1, bs.readBit());
bs.clear();
assertEquals(-1, bs.readBit());
assertEquals(0, bs.available());
assertEquals(2, bs.bytes());
bs.writeBit(1);
byte[] data = bs.toByteArray();
assertEquals(1, data[0]);
assertEquals(1, bs.available());
assertEquals(2, bs.bytes());
}
@Test
public void testSkipWrite() throws Exception {
BitStream bs0 = new BitStream(1);
bs0.writeBit(1);
bs0.alignWrite();
bs0.writeBit(1);
BitStream bs1 = new BitStream(1);
bs1.writeBit(1);
bs1.skipWrite(7);
bs1.writeBit(1);
byte[] data = new byte[] { 1, 1 };
assertArrayEquals(data, bs0.toByteArray());
assertArrayEquals(data, bs1.toByteArray());
try {
bs0.skipWrite(-1);
fail();
} catch (IllegalArgumentException ex) {
}
}
@Test
public void testSkipRead() throws Exception {
byte[] data = new byte[] { 1, 1 };
BitStream bs0 = new BitStream(data, 0, Byte.SIZE * data.length);
BitStream bs1 = new BitStream(data, 0, Byte.SIZE * data.length);
assertEquals(1, bs0.readBit());
assertEquals(1, bs1.readBit());
bs0.alignRead();
bs1.skipRead(7);
assertEquals(1, bs0.readBit());
assertEquals(1, bs1.readBit());
assertEquals(7, bs0.available());
assertEquals(7, bs1.available());
assertEquals(7, bs0.skipRead(7));
assertEquals(0, bs0.available());
assertEquals(0, bs0.skipRead(7));
try {
bs0.skipRead(-1);
fail();
} catch (IllegalArgumentException ex) {
}
}
}