/** 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] ); } } }