/** 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 Sep 4, 2007 */ package com.bigdata.rawstore; import java.util.Random; import junit.framework.TestCase; /** * Test suite for {@link WormAddressManager}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class TestWormAddressManager extends TestCase { Random r = new Random(); /** * */ public TestWormAddressManager() { super(); } public TestWormAddressManager(String name) { super(name); } // /** // * verify that the constructor correctly interprets is parameter as the #of // * offset bits. // */ // public void test_ctor_default() { // // WormAddressManager am = new WormAddressManager(); // // assertEquals("offsetBits", WormAddressManager.DEFAULT_OFFSET_BITS, // am.getOffsetBits() ); // // assertEquals("maxOffset", 4 * Bytes.terabyte - 1, am.getMaxOffset()); // // assertEquals("maxByteCount", 4 * Bytes.megabyte - 1, am.getMaxByteCount()); // // } /** * Test of constructor when splitting the long integer into two 32-bit * unsigned integer components (offset and #bytes). */ public void test_ctor_32bits() { WormAddressManager am = new WormAddressManager(32); assertEquals("offsetBits",32,am.offsetBits); assertEquals("byteCountBits",32,am.byteCountBits); assertEquals("maxOffset",0xffffffffL,am.maxOffset); /* * Note: 32 _unsigned_ bits can actually store more than can fit into a * signed 32-bit integer so we are modeling the limit as a long integer. */ assertEquals("maxByteCount",0xffffffffL,am.maxByteCount); // private static final transient long OFFSET_MASK = 0xffffffff00000000L; assertEquals("offsetMask",0xffffffff00000000L,am.offsetMask); // private static final transient long NBYTES_MASK = 0x00000000ffffffffL; assertEquals("byteCountMask",0x00000000ffffffffL,am.byteCountMask); } /** * Test of constructor when splitting the long integer into a 48-bit * unsigned integer (offset) and a 16-bit unsigned integer (#bytes). */ public void test_ctor_48bits() { WormAddressManager am = new WormAddressManager(48); assertEquals("offsetBits",48,am.offsetBits); assertEquals("byteCountBits",16,am.byteCountBits); assertEquals("maxOffset",0xffffffffffffL,am.maxOffset); assertEquals("maxByteCount",0xffffL,am.maxByteCount); assertEquals("offsetMask",0xffffffffffff0000L,am.offsetMask); assertEquals("byteCountMask",0x000000000000ffffL,am.byteCountMask); } public void test_ctor_correctRejection() { try { new WormAddressManager(0); fail("Expecting: "+IllegalArgumentException.class); } catch(IllegalArgumentException ex) { System.err.println("Ignoring expected exception: "+ex); } try { new WormAddressManager(-1); fail("Expecting: "+IllegalArgumentException.class); } catch(IllegalArgumentException ex) { System.err.println("Ignoring expected exception: "+ex); } try { new WormAddressManager(WormAddressManager.MIN_OFFSET_BITS-1); fail("Expecting: "+IllegalArgumentException.class); } catch(IllegalArgumentException ex) { System.err.println("Ignoring expected exception: "+ex); } try { new WormAddressManager(WormAddressManager.MAX_OFFSET_BITS+1); fail("Expecting: "+IllegalArgumentException.class); } catch(IllegalArgumentException ex) { System.err.println("Ignoring expected exception: "+ex); } /* * and verify that the end points of the range are legal. */ new WormAddressManager(WormAddressManager.MIN_OFFSET_BITS); new WormAddressManager(WormAddressManager.MAX_OFFSET_BITS); } /** * Verify that {@link WormAddressManager#toAddr(int, long)} will reject a zero * byte count or a zero offset since the value <code>0L</code> is reserved * to represent a null reference. */ public void test_canNotConstructNULLs() { for(int i=WormAddressManager.MIN_OFFSET_BITS; i<=WormAddressManager.MAX_OFFSET_BITS; i++) { WormAddressManager am = new WormAddressManager( i ); try { am.toAddr(0/*nbytes*/, 1L/*offset*/); } catch(IllegalArgumentException ex) { System.err.println("Ignoring expected exception: "+ex); } try { am.toAddr(1/*nbytes*/, 0L/*offset*/); } catch(IllegalArgumentException ex) { System.err.println("Ignoring expected exception: "+ex); } } } /** * Test of encoding and decoding addressed with a small set of 32 bit * offsets. */ public void test_get_32() { doTestGet(32, 1000); } /** * Test of encoding and decoding addressed with a small set of 48 bit * offsets. */ public void test_get_48() { doTestGet(48, 1000); } /** * Test of encoding and decoding addresses with a set of addresses selected * from each of the legal values of the offsetBits. */ public void test_get() { for (int i = WormAddressManager.MIN_OFFSET_BITS; i <= WormAddressManager.MAX_OFFSET_BITS; i++) { doTestGet(i, 10000); } } // /** // * Test of packing and unpacking addresses with a small set of addresses // * using 32 bit offsets. // */ // public void test_packUnpack_32() throws IOException { // // doTestPackUnpack(32, 100); // // } // // /** // * Test of packing and unpacking addresses with a small set of addresses // * using 48 bit offsets. // */ // public void test_packUnpack_48() throws IOException { // // doTestPackUnpack(48, 100); // // } // // /** // * Test of packing and unpacking addresses with a set of addresses selected // * from each of the legal values of the offsetBits. // */ // public void test_packUnpack() throws IOException { // // for (int i = WormAddressManager.MIN_OFFSET_BITS; i <= WormAddressManager.MAX_OFFSET_BITS; i++) { // // doTestPackUnpack(i, 10000); // // } // // } /** * Helper performs random tests of {@link WormAddressManager#toAddr(int, long)} * and is intended to verify both consistency of encoding and decoding and * correct rejection when encoding. * * @param offsetBits */ public void doTestGet(int offsetBits,int limit) { WormAddressManager am = new WormAddressManager(offsetBits); for(int i=0; i<limit; i++) { /* * next #of bytes in [0:maxByteCount], but never more tha * Integer.MAX_VALUE bytes. */ final int nbytes = r .nextInt((am.byteCountBits >= 32 ? Integer.MAX_VALUE : (int) am.maxByteCount)); /* * Any long value, but when 0, negative, or too large then we will * check for correct rejection. */ final long offset = r.nextLong(); if (nbytes < 0 || offset > am.maxOffset || offset < 0L) { /* * Check for correct rejection. */ try { am.toAddr(nbytes, offset); fail("Expecting: " + IllegalArgumentException.class + " (nbytes=" + nbytes + ", offset=" + offset + ")"); } catch(IllegalArgumentException ex) { // Ignoring expected exception. } } else { long addr = am.toAddr(nbytes, offset); assertEquals("nbytes",nbytes,am.getByteCount(addr)); assertEquals("offset",offset,am.getOffset(addr)); } } } // /** // * Helper method verifies (de-)serialization of addresses. // * // * @param offsetBits // * // * @throws IOException // */ // public void doTestPackUnpack(int offsetBits,int limit) throws IOException { // // WormAddressManager am = new WormAddressManager(offsetBits); // // /* // * Generate an array of valid encoded addresses, including a bunch that // * are NULLs. // */ // long[] addrs = new long[limit]; // // for(int i=0; i<limit; i++) { // // long addr; // // if (r.nextInt(100) < 5) { // // // 5% are NULLs. // addr = 0L; // // } else { // // addr = nextAddr(r,am); // //// System.err.print("."); // // } // // addrs[i] = addr; // // } // //// System.err.println("Generated "+limit+" addresses."); // // /* // * Pack the generated addresses. // */ // final byte[] packed; // { // // /* // * The result should tend to be significantly smaller than directly // * writing 8 bytes per address into the store, but of course that // * depends on the actual addresses. // */ // ByteArrayOutputStream baos = new ByteArrayOutputStream(limit*Bytes.SIZEOF_LONG); // // DataOutputStream os = new DataOutputStream(baos); // // for (int i = 0; i < limit; i++) { // // long addr = addrs[i]; // // try { // // am.packAddr(os, addr); // // } catch(IllegalArgumentException ex) { // // fail("Could not pack addr="+addr+"("+am.toString(addr)+") : "+ex); // // } // // } // // os.flush(); // // packed = baos.toByteArray(); // // } // // /* // * @todo compute and show the compression ratio but note that it will // * not be good when the addresses are choosen randomly. // */ //// System.err.println("Compression ratio: "+(limit)) // // /* // * Verify that the addresses unpack correctly. // */ // { // // DataInputStream in = new DataInputStream(new ByteArrayInputStream( // packed)); // // for (int i = 0; i < limit; i++) { // // long addr = am.unpackAddr(in); // // assertEquals(addrs[i],addr); // // } // // } // // } /** * Returns a legal random address and NULL 5% of the time. */ static public long nextAddr(Random r,WormAddressManager am) { if(r.nextInt(100)<5) return 0L; return nextNonZeroAddr(r,am); } /** * Returns a legal random non-NULL address. */ static public long nextNonZeroAddr(Random r,WormAddressManager am) { final int nbytes = nextNonZeroByteCount(r,am); final long offset = nextNonZeroOffset(r,am); assertEncodeDecode(am,nbytes,offset); final long addr = am.toAddr(nbytes, offset); return addr; } /** * Returns a legal random non-NULL address that does not extend as far as * <i>limit</i>. * * @param limit * The first byte that must not be covered by the returned * address. * * @todo this does not excercise the entire possible range addresses that * would satisify the contract of this method. */ static public long nextNonZeroAddr(Random r,WormAddressManager am, long limit) { final long offset = r.nextInt((int)Math.min(Integer.MAX_VALUE,limit)); final int nbytes = r.nextInt((int) Math.min(am.getMaxByteCount(), Math .min(Integer.MAX_VALUE, limit - offset))); assert offset + nbytes < limit; assertEncodeDecode(am,nbytes,offset); final long addr = am.toAddr(nbytes, offset); return addr; } /** * Verify that we can encode and decode that address. * * @param am * The address manager. * @param nbytes * The ground truth byte count. * @param offset * The ground truth byte offset. */ static void assertEncodeDecode(WormAddressManager am, int nbytes, long offset) { final long addr = am.toAddr(nbytes, offset); if (nbytes != am.getByteCount(addr)) { fail("offsetBits=" + am.offsetBits + ", addr=" + addr + ", expected nbytes=" + nbytes + ", actual=" + am.getByteCount(addr) + ", offset=" + offset); } if (offset != am.getOffset(addr)) { fail("offsetBits=" + am.offsetBits + ", addr=" + addr + ", expected offset=" + offset + ", actual=" + am.getOffset(addr) + ", nbytes=" + nbytes); } } /** * Next random byte count in [0:maxByteCount], but never more than * {@link Integer#MAX_VALUE} bytes and zero (0) 5% of the time. */ static public int nextByteCount(Random r, WormAddressManager am) { if(r.nextInt(100)<5) { return 0; } final int nbytes = nextNonZeroByteCount(r, am); return nbytes; } /** * Next random legal non-zero byte count less than maxByteCount and never * more than {@link Integer#MAX_VALUE} bytes. */ static public int nextNonZeroByteCount(Random r, WormAddressManager am) { int nbytes = 0; while(nbytes==0) { nbytes = r .nextInt((am.byteCountBits >= 32 ? Integer.MAX_VALUE : (int) am.maxByteCount)); } return nbytes; } /** * Next random byte offset and <code>0L</code> 5% of the time. */ static public long nextOffset(Random r, WormAddressManager am) { if(r.nextInt()<5) return 0L; return nextNonZeroOffset(r, am); } /** * Next non-zero random byte offset less than maxOffset. */ static public long nextNonZeroOffset(Random r, WormAddressManager am) { long offset = 0L; while (offset == 0L) { /* * start with any random long value and then mask off the bits that * are not allowed and shift down to to cover the zeros that leaves * in the lower order bits. */ long offset1 = r.nextLong(); long offset2 = offset1 & am.offsetMask; offset = offset2 >>> am.byteCountBits; } assert offset <= am.maxOffset; return offset; } /** * One of a series of tests of encoding and decoding addresses that have * proven themselves to be edge conditions in the code. */ public void test_encodeDecode_offsetBits32_01() { WormAddressManager am = new WormAddressManager(32); final int nbytes = 1537183690; final long offset = 3154105902L; final long addr = am.toAddr(nbytes, offset); assertEquals("offset",offset,am.getOffset(addr)); assertEquals("nbytes",nbytes,am.getByteCount(addr)); } public void test_encodeDecode_offsetBits48_01() { WormAddressManager am = new WormAddressManager(48); final int nbytes = 55065; final long offset = 227799501816710L; final long addr = am.toAddr(nbytes, offset); assertEquals("offset",offset,am.getOffset(addr)); assertEquals("nbytes",nbytes,am.getByteCount(addr)); } public void test_static_getMaxByteCount() { /* * edge case - 32 unsigned bits can not be represented in a Java signed * integer so the max byte count is limited by the API to the maximum * value of a signed integer. */ assertEquals("maxByteCount", Integer.MAX_VALUE, WormAddressManager .getMaxByteCount(32)); /* * 31 unsigned bits can represent the Java signed int maximum value. */ assertEquals("maxByteCount", Integer.MAX_VALUE, WormAddressManager .getMaxByteCount(33)); /* * And check a few other data points. */ assertEquals("maxByteCount", 4194304, WormAddressManager .getMaxByteCount(42)); assertEquals("maxByteCount", 1024, WormAddressManager .getMaxByteCount(54)); } /** * Test that {@link IAddressManager#toAddr(int, long)} will correctly reject * requests where the byte count is invalid. */ public void test_toAddr_correctRejection_byteCount() { WormAddressManager am = new WormAddressManager(48); // correct rejection of a negative byte count. try { am.toAddr(-1,0L); fail("Expecting: "+IllegalArgumentException.class); } catch(IllegalArgumentException ex) { System.err.println("Ingoring expected exception: "+ex); } /* * correct acceptance of the maximum byte count. */ am.toAddr(am.getMaxByteCount(),0L); // correct rejection of the maximum byte count plus one. try { am.toAddr(am.getMaxByteCount()+1,0L); fail("Expecting: "+IllegalArgumentException.class); } catch(IllegalArgumentException ex) { System.err.println("Ingoring expected exception: "+ex); } } /** * Test that {@link IAddressManager#toAddr(int, long)} will correctly reject * requests where the byte offset is invalid. */ public void test_toAddr_correctRejection_byteOffset() { WormAddressManager am = new WormAddressManager(48); // correct rejection of a negative byte offset. try { am.toAddr(1,-1L); fail("Expecting: "+IllegalArgumentException.class); } catch(IllegalArgumentException ex) { System.err.println("Ingoring expected exception: "+ex); } /* * correct acceptance of the maximum byte offset. */ am.toAddr(1,am.getMaxOffset()); // correct rejection of the maximum byte offset plus one. try { am.toAddr(1,am.getMaxOffset()+1); fail("Expecting: "+IllegalArgumentException.class); } catch(IllegalArgumentException ex) { System.err.println("Ingoring expected exception: "+ex); } } }