/** 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.DataOutputStream; import java.io.IOException; import java.util.Random; import junit.framework.TestCase; /** * TestAll suite for {@link ShortPacker}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class ShortPackerTestCase extends TestCase { /** * */ public ShortPackerTestCase() { super(); } /** * @param name */ public ShortPackerTestCase(String name) { super(name); } /** * Unpacks a short value. * * @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( short expected, byte[] packed ) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream( packed ); short actual = ShortPacker.unpackShort( (DataInput) new DataInputStream( bais ) ); assertEquals( "value", expected, actual ); assertTrue( "Expecting EOF", bais.read() == -1 ); } /* * A short in [0:127] is packed into one byte. Larger values are packed into * two bytes. The high bit of the first byte is set if the value was packed * into two bytes. If the bit is set, clear the high bit, read the next * byte, and interpret the two bytes as a short value. Otherwise interpret * the byte as a short value. */ public void testUnpack() throws IOException { doUnpackTest( (short) 0x0, new byte[]{(byte)0x0} ); doUnpackTest( (short) 0x1, new byte[]{(byte)0x1} ); doUnpackTest( (short) 0x7, new byte[]{(byte)0x7} ); doUnpackTest( (short) 0xf, new byte[]{(byte)0xf} ); doUnpackTest( (short) 0x70, new byte[]{(byte)0x70} ); doUnpackTest( (short) 0x7f, new byte[]{(byte)0x7f} ); doUnpackTest( (short) 0x80, new byte[]{(byte)0x80, (byte)0x80} ); doUnpackTest( (short) 0x8e, new byte[]{(byte)0x80, (byte)0x8e} ); } public void doPackTest( short v, byte[] expected ) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); final int nbytes = ShortPacker.packShort( new DataOutputStream( baos ), v ); final byte[] actual = baos.toByteArray(); assertEquals( "nbytes", expected.length, nbytes ); assertEquals( "bytes", expected, actual ); } public void testPack() throws IOException { // [0:127] should be packed into one byte. doPackTest( (short) 0x00, new byte[]{(byte)0x0} ); doPackTest( (short) 0x01, new byte[]{(byte)0x1} ); doPackTest( (short) 0x02, new byte[]{(byte)0x2} ); doPackTest( (short) 0x0e, new byte[]{(byte)0xe} ); doPackTest( (short) 0x0f, new byte[]{(byte)0xf} ); doPackTest( (short) 0x10, new byte[]{(byte)0x10 }); doPackTest( (short) 0x11, new byte[]{(byte)0x11 }); doPackTest( (short) 0x1f, new byte[]{(byte)0x1f }); doPackTest( (short) 0x20, new byte[]{(byte)0x20 }); doPackTest( (short) 0x70, new byte[]{(byte)0x70 }); doPackTest( (short) 0x7f, new byte[]{(byte)0x7f }); // high nibble of the short value is zero. doPackTest( (short) 0x80, new byte[]{(byte)0x80, (byte)0x80 }); doPackTest( (short) 0xff, new byte[]{(byte)0x80, (byte)0xff }); doPackTest( (short) 0x100, new byte[]{(byte)0x81, (byte)0x00 }); doPackTest( (short) 0x101, new byte[]{(byte)0x81, (byte)0x01 }); doPackTest( (short) 0x121, new byte[]{(byte)0x81, (byte)0x21 }); doPackTest( (short) 0x1ee, new byte[]{(byte)0x81, (byte)0xee }); doPackTest( (short) 0x1ff, new byte[]{(byte)0x81, (byte)0xff }); doPackTest( (short) 0xfff, new byte[]{(byte)0x8f, (byte)0xff }); } public void test_rejectNegatives() throws IOException { try { doPackTest( (short) 0x8fff, new byte[]{}); fail( "Expecting: "+IllegalArgumentException.class ); } catch( IllegalArgumentException ex ) { System.err.println( "Ignoring expected exception: "+ex ); } try { doPackTest( (short) 0xffff, new byte[]{}); fail( "Expecting: "+IllegalArgumentException.class ); } catch( IllegalArgumentException ex ) { System.err.println( "Ignoring expected exception: "+ex ); } } public static final long SIGN_MASK = 11<<15; public void testHighBit() { assertTrue( "sign bit", ( (short)-1 & SIGN_MASK ) != 0 ); assertFalse( "sign bit", ( (short) 0 & SIGN_MASK ) != 0 ); } private interface ShortGenerator { public short nextShort(); } /** * All long values in sequence starting from the given start value * and using the given increment. * @author thompsonbry */ private static class Sequence implements ShortGenerator { short _start, _inc, _next; public Sequence( short start, short inc ) { _start = start; _inc = inc; _next = start; } public short nextShort() { if( _next == Short.MAX_VALUE ) { throw new RuntimeException( "No more short values."); } short v = _next; _next += _inc; return v; } } /** * Random short values (16 bits of random short), including negatives, * with a uniform distribution. * * @author thompsonbry */ private static class RandomShort implements ShortGenerator { Random _rnd; public RandomShort( Random rnd ) { _rnd = rnd; } public short nextShort() { return (short)_rnd.nextInt(); // truncate. } } /** * Run a large #of pack/unpack operations on a sequence of short values to * demonstrate correctness in that sequence. The sequence is the short values * from -1 to {@link Short#MAX_VALUE} by one (dense coverage). * * @throws IOException */ public void testStressSequence() throws IOException { // dense coverage of the first 1M values. doStressTest( Short.MAX_VALUE+1, new Sequence( (short)-1, (short)1 ) ); } /** * Run a large #of random pack/unpack operations to sample the space while * showing correctness on those samples. * * @throws IOException */ public void testStressRandom() throws IOException { // test on random long values. doStressTest( 0xffff, new RandomShort( 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, ShortGenerator g ) throws IOException { long nwritten = 0L; long packlen = 0L; long minv = Short.MAX_VALUE, maxv = Short.MIN_VALUE; for( int i=0; i<ntrials; i++ ) { short expected = g.nextShort(); if( expected < 0L ) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream( baos ); try { ShortPacker.packShort( 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 ); int nbytesActual = ShortPacker.packShort( dos, expected ); byte[] packed = baos.toByteArray(); final int nbytesExpected = ShortPacker.getNBytes( packed[ 0 ] ); ByteArrayInputStream bais = new ByteArrayInputStream( packed ); DataInputStream dis = new DataInputStream( bais ); final short actual = ShortPacker.unpackShort( (DataInput)dis ); assertEquals( "trial="+i, expected, actual ); assertEquals( "trial="+i+", v="+expected+", nbytes", nbytesExpected, nbytesActual ); assertEquals( "trial="+i+", v="+expected+", nbytes", nbytesExpected, packed.length ); 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] ); } } }