/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Oct 28, 2005
*/
package com.bigdata.io;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Random;
import junit.framework.TestCase;
/**
* TestAll suite for {@link LongPacker}.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class LongPackerTestCase extends TestCase {
/**
*
*/
public LongPackerTestCase() {
super();
}
/**
* @param name
*/
public LongPackerTestCase(String name) {
super(name);
}
/**
* Unpacks a long.
*
* @param expected The expected long value.
*
* @param packed The packed byte[].
*
* @throws IOException
* If there was not enough data.
*
* @throws junit.framework.AssertionFailedError
* If there is too much data.
*/
public void doUnpackTest( long expected, byte[] packed )
throws IOException
{
ByteArrayInputStream bais = new ByteArrayInputStream( packed );
long actual = LongPacker.unpackLong( (DataInput)new DataInputStream( bais ) );
assertEquals( "value", expected, actual );
assertTrue( "Expecting EOF", bais.read() == -1 );
}
/**
* Given the first byte of a packed long value, return the #of bytes into which
* that value was packed (including this one).
*
* @param firstByte The first byte.
*
* @return The #of bytes. This is in the range [1:8] inclusive.
*/
public int getNBytes( int firstByte ) {
int nbytes;
if( ( firstByte & 0x80 ) != 0 ) { // high bit is set.
nbytes = 8;
} else {
nbytes = firstByte >> 4; // clear the high bit and right shift one nibble.
}
return nbytes;
}
public void testNBytes()
{
// high bit is set - always 8 bytes.
assertEquals( "nbytes", 8, getNBytes( 0x80 ) );
assertEquals( "nbytes", 8, getNBytes( 0x81 ) );
assertEquals( "nbytes", 8, getNBytes( 0x8e ) );
assertEquals( "nbytes", 8, getNBytes( 0x8f ) );
// high bit is NOT set. nbytes is the upper nibble.
assertEquals( "nbytes", 1, getNBytes( 0x10 ) );
assertEquals( "nbytes", 2, getNBytes( 0x20 ) );
assertEquals( "nbytes", 3, getNBytes( 0x30 ) );
assertEquals( "nbytes", 4, getNBytes( 0x40 ) );
assertEquals( "nbytes", 5, getNBytes( 0x50 ) );
assertEquals( "nbytes", 6, getNBytes( 0x60 ) );
assertEquals( "nbytes", 7, getNBytes( 0x70 ) );
// high bit is NOT set. nbytes is the upper nibble.
assertEquals( "nbytes", 1, getNBytes( 0x11 ) );
assertEquals( "nbytes", 2, getNBytes( 0x21 ) );
assertEquals( "nbytes", 3, getNBytes( 0x31 ) );
assertEquals( "nbytes", 4, getNBytes( 0x41 ) );
assertEquals( "nbytes", 5, getNBytes( 0x51 ) );
assertEquals( "nbytes", 6, getNBytes( 0x61 ) );
assertEquals( "nbytes", 7, getNBytes( 0x71 ) );
// high bit is NOT set. nbytes is the upper nibble.
assertEquals( "nbytes", 1, getNBytes( 0x1f ) );
assertEquals( "nbytes", 2, getNBytes( 0x2f ) );
assertEquals( "nbytes", 3, getNBytes( 0x3f ) );
assertEquals( "nbytes", 4, getNBytes( 0x4f ) );
assertEquals( "nbytes", 5, getNBytes( 0x5f ) );
assertEquals( "nbytes", 6, getNBytes( 0x6f ) );
assertEquals( "nbytes", 7, getNBytes( 0x7f ) );
}
public void testUnpack()
throws IOException
{
// upper nibble is 1, so nbytes == 1 and the lower nibble is the value.
doUnpackTest( 0x0, new byte[]{(byte)0x10} );
doUnpackTest( 0x1, new byte[]{(byte)0x11} );
doUnpackTest( 0x7, new byte[]{(byte)0x17} );
doUnpackTest( 0xf, new byte[]{(byte)0x1f} );
// upper nibble is 2, so nbytes == 2.
doUnpackTest( 0xf00, new byte[]{(byte)0x2f, (byte)0x00} );
doUnpackTest( 0xfa7, new byte[]{(byte)0x2f, (byte)0xa7} );
doUnpackTest( 0xfa0, new byte[]{(byte)0x2f, (byte)0xa0} );
doUnpackTest( 0xf07, new byte[]{(byte)0x2f, (byte)0x07} );
// upper nibble is 3, so nbytes == 3.
doUnpackTest( 0xcaa4d, new byte[]{(byte)0x3c, (byte)0xaa, (byte)0x4d} );
// high bit only, lower nibble plus next seven bytes are the value.
doUnpackTest( 0xaeede00a539271fL,
new byte[]{(byte)0x8a, (byte)0xee, (byte)0xde, (byte)0x00,
(byte)0xa5, (byte)0x39, (byte)0x27, (byte)0x1f
}
);
}
public void doPackTest( long v, byte[] expected )
throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
final int nbytes = LongPacker.packLong( (DataOutput)new DataOutputStream( baos ), v );
final byte[] actual = baos.toByteArray();
assertEquals( "nbytes", expected.length, nbytes );
assertEquals( "nbytes", getNBytes(expected[0]), nbytes );
assertEquals( "bytes", expected, actual );
}
public void test_getNibbleLength()
{
// Note: zero (0) is interpreted as being one nibble for our purposes.
assertEquals( "nibbles", 1, LongPacker.getNibbleLength( 0x0 ) );
assertEquals( "nibbles", 1, LongPacker.getNibbleLength( 0x1 ) );
assertEquals( "nibbles", 1, LongPacker.getNibbleLength( 0x2 ) );
assertEquals( "nibbles", 1, LongPacker.getNibbleLength( 0x7 ) );
assertEquals( "nibbles", 1, LongPacker.getNibbleLength( 0x8 ) );
assertEquals( "nibbles", 1, LongPacker.getNibbleLength( 0xe ) );
assertEquals( "nibbles", 1, LongPacker.getNibbleLength( 0xf ) );
assertEquals( "nibbles", 2, LongPacker.getNibbleLength( 0x10 ) );
assertEquals( "nibbles", 2, LongPacker.getNibbleLength( 0x11 ) );
assertEquals( "nibbles", 2, LongPacker.getNibbleLength( 0x12 ) );
assertEquals( "nibbles", 2, LongPacker.getNibbleLength( 0x17 ) );
assertEquals( "nibbles", 2, LongPacker.getNibbleLength( 0x18 ) );
assertEquals( "nibbles", 2, LongPacker.getNibbleLength( 0x1e ) );
assertEquals( "nibbles", 2, LongPacker.getNibbleLength( 0x1f ) );
assertEquals( "nibbles", 2, LongPacker.getNibbleLength( 0x7f ) );
assertEquals( "nibbles", 2, LongPacker.getNibbleLength( 0x8f ) );
assertEquals( "nibbles", 2, LongPacker.getNibbleLength( 0xff ) );
assertEquals( "nibbles", 3, LongPacker.getNibbleLength( 0x100 ) );
assertEquals( "nibbles", 3, LongPacker.getNibbleLength( 0x101 ) );
assertEquals( "nibbles", 3, LongPacker.getNibbleLength( 0x121 ) );
assertEquals( "nibbles", 3, LongPacker.getNibbleLength( 0x1ee ) );
assertEquals( "nibbles", 3, LongPacker.getNibbleLength( 0x1ff ) );
assertEquals( "nibbles", 3, LongPacker.getNibbleLength( 0xfff ) );
assertEquals( "nibbles", 4, LongPacker.getNibbleLength( 0x1ff0 ) );
assertEquals( "nibbles", 4, LongPacker.getNibbleLength( 0x7ff0 ) );
assertEquals( "nibbles", 4, LongPacker.getNibbleLength( 0xfff0 ) );
assertEquals( "nibbles", 4, LongPacker.getNibbleLength( 0xfff1 ) );
assertEquals( "nibbles", 5, LongPacker.getNibbleLength( 0x12345 ) );
assertEquals( "nibbles", 5, LongPacker.getNibbleLength( 0x54321 ) );
assertEquals( "nibbles", 6, LongPacker.getNibbleLength( 0x123456 ) );
assertEquals( "nibbles", 6, LongPacker.getNibbleLength( 0x654321 ) );
assertEquals( "nibbles", 7, LongPacker.getNibbleLength( 0x1234567 ) );
assertEquals( "nibbles", 7, LongPacker.getNibbleLength( 0x7654321 ) );
/*
* Note: At 8 nibbles we have 32 bits. When the high bit is one, this
* MUST be expressed as a long (trailing 'L', NOT cast to a long) or it
* will be interpreted as a negative integer and sign extended to a
* negative long.
*/
assertEquals( "nibbles", 8, LongPacker.getNibbleLength( 0x12345678L ) );
assertEquals( "nibbles", 8, LongPacker.getNibbleLength( 0x87654321L ) );
assertEquals( "nibbles", 9, LongPacker.getNibbleLength( 0x123456789L ) );
assertEquals( "nibbles", 9, LongPacker.getNibbleLength( 0x987654321L ) );
assertEquals( "nibbles", 10, LongPacker.getNibbleLength( 0x123456789aL ) );
assertEquals( "nibbles", 10, LongPacker.getNibbleLength( 0xa987654321L ) );
assertEquals( "nibbles", 11, LongPacker.getNibbleLength( 0x123456789abL ) );
assertEquals( "nibbles", 11, LongPacker.getNibbleLength( 0xba987654321L ) );
assertEquals( "nibbles", 12, LongPacker.getNibbleLength( 0x123456789abcL ) );
assertEquals( "nibbles", 12, LongPacker.getNibbleLength( 0xcba987654321L ) );
assertEquals( "nibbles", 13, LongPacker.getNibbleLength( 0x123456789abcdL ) );
assertEquals( "nibbles", 13, LongPacker.getNibbleLength( 0xdcba987654321L ) );
assertEquals( "nibbles", 14, LongPacker.getNibbleLength( 0x123456789abcdeL ) );
assertEquals( "nibbles", 14, LongPacker.getNibbleLength( 0xedcba987654321L ) );
assertEquals( "nibbles", 15, LongPacker.getNibbleLength( 0x123456789abcdefL ) );
assertEquals( "nibbles", 15, LongPacker.getNibbleLength( 0xfedcba987654321L ) );
assertEquals( "nibbles", 16, LongPacker.getNibbleLength( 0x1234567890abcdefL ) );
assertEquals( "nibbles", 16, LongPacker.getNibbleLength( 0xfedcba0987654321L ) );
}
public void testPack()
throws IOException
{
// [0:15] should be packed into one byte.
doPackTest( 0x0, new byte[]{(byte)0x10} );
doPackTest( 0x1, new byte[]{(byte)0x11} );
doPackTest( 0x2, new byte[]{(byte)0x12} );
doPackTest( 0xe, new byte[]{(byte)0x1e} );
doPackTest( 0xf, new byte[]{(byte)0x1f} );
/*
* 0x10 through 0xfff overflow the lower nibble, so the value is packed
* into two bytes. the first byte has the header and the next three
* nibbles are the value. This case is good for up to 2^12, since there
* are three full nibbles to encode the value.
*/
doPackTest( 0x10, new byte[]{(byte)0x20, (byte)0x10 });
doPackTest( 0x11, new byte[]{(byte)0x20, (byte)0x11 });
doPackTest( 0x1f, new byte[]{(byte)0x20, (byte)0x1f });
doPackTest( 0x20, new byte[]{(byte)0x20, (byte)0x20 });
doPackTest( 0xff, new byte[]{(byte)0x20, (byte)0xff });
doPackTest( 0x100, new byte[]{(byte)0x21, (byte)0x00 });
doPackTest( 0x101, new byte[]{(byte)0x21, (byte)0x01 });
doPackTest( 0x121, new byte[]{(byte)0x21, (byte)0x21 });
doPackTest( 0x1ee, new byte[]{(byte)0x21, (byte)0xee });
doPackTest( 0x1ff, new byte[]{(byte)0x21, (byte)0xff });
doPackTest( 0xfff, new byte[]{(byte)0x2f, (byte)0xff });
/*
* 0x1000 through 0xfffff fit into one more byte.
*/
doPackTest( 0x1000, new byte[]{(byte)0x30, (byte)0x10, (byte)0x00 });
doPackTest( 0x1234, new byte[]{(byte)0x30, (byte)0x12, (byte)0x34 });
doPackTest( 0x1fff, new byte[]{(byte)0x30, (byte)0x1f, (byte)0xff });
doPackTest( 0x54321, new byte[]{(byte)0x35, (byte)0x43, (byte)0x21 });
doPackTest( 0xfffff, new byte[]{(byte)0x3f, (byte)0xff, (byte)0xff });
}
public static final long SIGN_MASK = 1L<<63;
public void testHighBit() {
assertTrue( "sign bit", ( -1L & SIGN_MASK ) != 0 );
assertFalse( "sign bit", ( 0L & SIGN_MASK ) != 0 );
}
private interface LongGenerator
{
public long nextLong();
}
/**
* All long values in sequence starting from the given start value
* and using the given increment.
* @author thompsonbry
*/
private static class Sequence implements LongGenerator
{
long _start, _inc, _next;
public Sequence( long start, long inc ) {
_start = start;
_inc = inc;
_next = start;
}
public long nextLong() {
long v = _next;
_next += _inc;
return v;
// double d = rnd.nextGaussian();
//// if( d < 0 ) d = -d;
// final long expected = (long) ( d * Long.MAX_VALUE );
}
}
/**
* Random long values (32 bits of random long), including negatives,
* with a uniform distribution.
*
* @author thompsonbry
*/
private static class RandomLong implements LongGenerator
{
Random _rnd;
public RandomLong( Random rnd ) {
_rnd = rnd;
}
public long nextLong() {
return _rnd.nextLong();
}
}
/**
* Run a large #of pack/unpack operations on a sequence of long values to
* demonstrate correctness in that sequence. The sequence is the long
* values from -1 to 1M by one (dense coverage).
*
* @throws IOException
*/
public void testStressSequence() throws IOException {
// dense coverage of the first 1M values.
doStressTest( 1000000, new Sequence( -1, 1 ) );
}
/**
* Run a large #of random pack/unpack operations to sample the space while
* showing correctness on those samples. The amount of compression due to
* packing for this test is <em>very</em> small since all bits are equally
* likely to be non-zero, so the #of bytes required on average to pack a
* long value is 8.
*
* @throws IOException
*/
public void testStressRandom() throws IOException {
// test on 1M random long values.
doStressTest( 1000000, new RandomLong( new Random() ) );
}
/**
* Run a stress test. Writes some information of possible interest onto
* System.err.
*
* @param ntrials #of trials.
*
* @param g Generator for the long values.
*
* @throws IOException
*/
public void doStressTest( int ntrials, LongGenerator g ) throws IOException {
long nwritten = 0L;
long packlen = 0L;
long minv = Long.MAX_VALUE, maxv = Long.MIN_VALUE;
for( int i=0; i<ntrials; i++ ) {
long expected = g.nextLong();
if( expected < 0L ) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream( baos );
try {
LongPacker.packLong( (DataOutput)dos, expected );
fail( "Expecting rejection of negative value: val="+expected );
}
catch( IllegalArgumentException ex ) {
// System.err.println( "Ingoring expected exception: "+ex );
}
} else {
if( expected > maxv ) maxv = expected;
if( expected < minv ) minv = expected;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream( baos );
// final int expectedByteLength = getNBytes( expected );
//
final int actualByteLength1 = LongPacker.packLong( (DataOutput) dos, expected );
byte[] packed = baos.toByteArray();
final int actualByteLength2 = getNBytes( packed[ 0 ] );
ByteArrayInputStream bais = new ByteArrayInputStream( packed );
DataInputStream dis = new DataInputStream( bais );
final long actual = LongPacker.unpackLong( (DataInput) dis );
assertEquals( "trial="+i, expected, actual );
assertEquals( "trial="+i+", v="+expected+", nbytes", actualByteLength1, actualByteLength2 );
packlen += packed.length; // total #of packed bytes.
nwritten++; // count #of non-negative random values.
}
}
System.err.println( "\nWrote "+nwritten+" non-negative long values." );
System.err.println( "minv="+minv+", maxv="+maxv );
System.err.println( "#packed bytes ="+packlen );
System.err.println( "#bytes if not packed="+(nwritten * 8));
long nsaved = ( nwritten * 8 ) - packlen;
System.err.println ("#bytes saved ="+nsaved);
System.err.println( "%saved by packing ="+nsaved/(nwritten*8f)*100+"%");
}
public static void assertEquals( String msg, byte[] expected, byte[] actual )
{
assertEquals( msg+": length", expected.length, actual.length );
for( int i=0; i<expected.length; i++ ) {
assertEquals( msg+": byte[i="+i+"]", expected[i], actual[i] );
}
}
}