/**
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 Nov 12, 2006
*/
package com.bigdata.btree.raba;
import junit.framework.TestCase2;
import com.bigdata.util.BytesUtil;
/**
* Unit tests for {@link IKeyBuffer#search(byte[] searchKey)}.
*
* @todo write performance test? the existing code can no longer be used since
* both linear and binary searches first test the shared prefix for the keys and
* then search on the remainder... perhaps it could be used by elevating the
* method to test the prefix into the performance test. another twist for the
* performance test would be testing for loop unrolling conditions (N<~4) and
* testing JNI vs pure Java for the comparison functions.
*
* @todo do version of test with negative and positive integer keys so that the
* search on the encoded keys detects whether signed bytes or unsigned
* bytes are being compared.
*
* @todo do tests with JNI code linked in. note that we only use
* {@link BytesUtil#compareBytesWithLenAndOffset(int, int, byte[], int, int, byte[])} for
* searching on the key buffers since that allows non-zero offsets into
* the search key.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class TestKeyBufferSearch extends TestCase2 {
public TestKeyBufferSearch() {
}
public TestKeyBufferSearch(String name) {
super(name);
}
// public static Test suite() {
//
// TestSuite suite = new TestSuite("IKeyBuffer.search");
//
// suite.addTestSuite(TestBinarySearch.class);
// suite.addTestSuite(TestLinearSearch.class);
//
// return suite;
//
// }
// /*
// * abstract search methods are implemented by subclasses for testing the
// * linear vs binary search code.
// */
public int search(AbstractKeyBuffer kbuf, byte[] key) {
return kbuf.search(key);
}
/**
* Test search for keys using both a mutable and an immutable key buffer and
* a known set of keys.
*/
public void test_search01()
{
byte[][] keys = new byte[5][];
int i = 0;
keys[i++] = new byte[]{5}; // offset := 0, insert before := -1
keys[i++] = new byte[]{7}; // offset := 1, insert before := -2
keys[i++] = new byte[]{9}; // offset := 2, insert before := -3
keys[i++] = new byte[]{11}; // offset := 3, insert before := -4
keys[i++] = new byte[]{13}; // offset := 4, insert before := -5
// insert after := -6
int nkeys = 5;
final MutableKeyBuffer kbuf = new MutableKeyBuffer(nkeys, keys);
doSearchTest01(kbuf);
// ImmutableKeyBuffer kbuf2 = new ImmutableKeyBuffer( kbuf );
//
// doSearchTest01(kbuf2);
}
/**
* Search using the specified buffer which must be pre-initialized with a
* known set of keys.
*
* @param kbuf The buffer to be searched.
*/
private void doSearchTest01(AbstractKeyBuffer kbuf) {
// The general formula for the record offset is:
//
// offset := sizeof(record) * ( index - 1 )
//
// The general formula for the insertion point is:
//
// insert := - ( offset + 1 )
//
// where [offset] is the offset of the record before which the
// new record should be inserted.
//
// verify offset of record found.
//
// Verify finds the first record in the array.
assertEquals(0, search(kbuf, new byte[]{5}));
// Verify finds the 2nd record in the array.
assertEquals(1, search(kbuf, new byte[]{7}));
// Verify finds the penultimate record in the array.
assertEquals(3, search(kbuf, new byte[]{11}));
// Verify finds the last record in the array.
assertEquals(4, search(kbuf, new byte[]{13}));
//
// verify insertion points (key not found).
//
// Verify insertion point for key less than any value in the
// array.
assertEquals(-1, search(kbuf, new byte[]{4}));
// Verify insertion point for key between first and 2nd
// records.
assertEquals(-2, search(kbuf, new byte[]{6}));
// Verify insertion point for key between penultimate and last
// records.
assertEquals(-5, search(kbuf, new byte[]{12}));
// Verify insertion point for key greater than the last record.
assertEquals(-6, search(kbuf, new byte[]{14}));
}
/**
* Tests with non-zero offset into a key buffer with a shared prefix
* of 3 bytes.
*/
public void test_search02() {
// build up keys in sorted order.
int nkeys = 3;
int maxKeys = 3;
byte[][] keys = new byte[nkeys][];
int i = 0;
keys[i++] = new byte[]{1,3,4}; // offset := 0, insert before := -1
keys[i++] = new byte[]{1,3,4,1,0}; // offset := 1, insert before := -2
keys[i++] = new byte[]{1,3,4,2}; // offset := 2, insert before := -3
// insert after := -4
{
MutableKeyBuffer kbuf = new MutableKeyBuffer(nkeys, keys);
assertEquals(3,kbuf.getPrefixLength());
doSearchTest02(kbuf);
try { // correct rejection when search key is null.
kbuf.search(null);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex);
}
}
// {
// ImmutableKeyBuffer kbuf = new ImmutableKeyBuffer(nkeys, maxKeys, keys);
//
// assertEquals(3,kbuf.getPrefixLength());
//
// doSearchTest02(kbuf);
//
// try { // correct rejection when search key is null.
// kbuf.search(null);
// fail("Expecting: " + IllegalArgumentException.class);
// } catch (IllegalArgumentException ex) {
// if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex);
// }
//
// }
}
private void doSearchTest02(final AbstractKeyBuffer kbuf) {
if(log.isInfoEnabled()) log.info("kbuf="+kbuf);
assertEquals(0,kbuf.search(new byte[]{1,3,4}));
assertEquals(1,kbuf.search(new byte[]{1,3,4,1,0}));
assertEquals(2,kbuf.search(new byte[]{1,3,4,2}));
// test search before/between/after keys.
assertEquals(-1,kbuf.search(new byte[]{1,3,3}));
assertEquals(-2,kbuf.search(new byte[]{1,3,4,0}));
assertEquals(-3,kbuf.search(new byte[]{1,3,4,1,1}));
assertEquals(-4,kbuf.search(new byte[]{1,3,4,3}));
}
/**
* Test with prefixLength of zero and various search keys.
*/
public void test_search03() {
int nkeys = 3;
int maxKeys = 3;
byte[][] keys = new byte[][] {
new byte[]{2,3,5},
new byte[]{4,5,6},
new byte[]{5,4,9}
};
doSearchTest03( new MutableKeyBuffer(nkeys,keys));
// doSearchTest03( new ImmutableKeyBuffer(nkeys,maxKeys,keys));
}
private void doSearchTest03(final AbstractKeyBuffer kbuf) {
assert kbuf.getPrefixLength() == 0;
assertEquals(0,kbuf.search( new byte[]{2,3,5}));
assertEquals(1,kbuf.search( new byte[]{4,5,6}));
assertEquals(2,kbuf.search( new byte[]{5,4,9}));
// test search before given keys.
assertEquals(-1,kbuf.search( new byte[]{1,3,3}));
assertEquals(-1,kbuf.search( new byte[]{2,3,3}));
assertEquals(-1,kbuf.search( new byte[]{2,3,4}));
assertEquals(-1,kbuf.search( new byte[]{2}));
assertEquals(-1,kbuf.search( new byte[]{0}));
assertEquals(-1,kbuf.search( new byte[]{}));
// test search between given keys.
assertEquals(-2,kbuf.search( new byte[]{2,3,5,0}));
assertEquals(-2,kbuf.search( new byte[]{4,5,5,9}));
assertEquals(-3,kbuf.search( new byte[]{4,5,6,0}));
assertEquals(-3,kbuf.search( new byte[]{5,4,8,9}));
// test search after given keys.
assertEquals(-4,kbuf.search( new byte[]{5,4,9,0}));
assertEquals(-4,kbuf.search( new byte[]{5,5}));
assertEquals(-4,kbuf.search( new byte[]{6}));
}
/**
* Test search on empty key buffer.
*/
public void test_search04() {
int nkeys = 0;
int maxKeys = 4;
byte[][] keys = new byte[][] {};
doSearchTest04( new MutableKeyBuffer(nkeys,keys));
// doSearchTest04( new ImmutableKeyBuffer(nkeys,0,keys));
//
// doSearchTest04( new ImmutableKeyBuffer(nkeys,maxKeys,keys));
}
private void doSearchTest04(final AbstractKeyBuffer kbuf) {
assertEquals(-1,kbuf.search(new byte[]{}));
assertEquals(-1,kbuf.search(new byte[]{9,9,9,9}));
}
public void test_prefixMatchLength() {
// build up keys in sorted order.
int nkeys = 3;
int maxKeys = 3;
byte[][] keys = new byte[nkeys][];
int i = 0;
keys[i++] = new byte[]{1,3,4}; // offset := 0, insert before := -1
keys[i++] = new byte[]{1,3,4,0,0}; // offset := 1, insert before := -2
keys[i++] = new byte[]{1,3,4,1}; // offset := 2, insert before := -3
// insert after := -4
{
MutableKeyBuffer kbuf = new MutableKeyBuffer(nkeys,keys);
doPrefixMatchLengthTest(kbuf);
}
// {
//
// ImmutableKeyBuffer kbuf = new ImmutableKeyBuffer(nkeys,maxKeys,keys);
//
// doPrefixMatchLengthTest(kbuf);
//
// }
}
private void doPrefixMatchLengthTest(AbstractKeyBuffer kbuf) {
// verify the prefix length.
final int prefixLength = 3;
assertEquals("prefixLength", prefixLength, kbuf.getPrefixLength());
// verify the prefix.
assertEquals("prefix", new byte[]{1,3,4}, kbuf.getPrefix());
if(log.isInfoEnabled()) log.info("prefix="+BytesUtil.toString(kbuf.getPrefix()));
/*
* test on some keys that are in the buffer. all keys in the buffer must
* match the entire shared prefix.
*/
assertEquals(0, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 3, 4 }));
assertEquals(0, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 3, 4, 0, 0 }));
assertEquals(0, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 3, 4, 1 }));
/*
* now test on some keys that also match the entire prefix but are not
* found in the buffer.
*/
assertEquals(0, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 3, 4, 7 }));
assertEquals(0, kbuf
._prefixMatchLength(prefixLength, new byte[] { 1, 3, 4, 0, 1, 3 }));
/*
* now test on some keys that order _before_ the prefix and hence before
* all keys in the buffer. we include cases where the search key is
* shorter than the prefix and cases when it is longer than the prefix.
*/
/*
* test search keys that have nothing in common with the prefix.
*/
assertEquals(-1, kbuf._prefixMatchLength(prefixLength, new byte[] { 0 }));
assertEquals(-1, kbuf._prefixMatchLength(prefixLength, new byte[] { 0, 0 }));
assertEquals(-1, kbuf._prefixMatchLength(prefixLength, new byte[] { 0, 0, 0 }));
assertEquals(-1, kbuf._prefixMatchLength(prefixLength, new byte[] { 0, 0, 0, 0 }));
/*
* test search keys that have only their first byte in common with the
* prefix.
*/
assertEquals(-1, kbuf._prefixMatchLength(prefixLength, new byte[] { 1 }));
assertEquals(-1, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 0 }));
assertEquals(-1, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 0, 0 }));
assertEquals(-1, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 0, 0, 0 }));
/*
* test search keys that have only their first two bytes in common with
* the prefix.
*/
assertEquals(-1, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 3 }));
assertEquals(-1, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 3, 0 }));
assertEquals(-1, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 3, 0, 0 }));
/*
* test search keys that match the entire prefix (three bytes).
*/
assertEquals(0, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 3, 4 }));
assertEquals(0, kbuf._prefixMatchLength(prefixLength, new byte[] { 1, 3, 4, 0 }));
/*
* now test on some keys that order _after_ the prefix and hence after
* all keys in the buffer. we include cases where the search key is
* shorter than the prefix and cases when it is longer than the prefix.
*/
assertEquals(-(3) - 1, kbuf._prefixMatchLength(prefixLength, new byte[] { 9 }));
assertEquals(-(3) - 1, kbuf._prefixMatchLength(prefixLength, new byte[] { 9, 9 }));
assertEquals(-(3) - 1, kbuf._prefixMatchLength(prefixLength, new byte[] { 9, 9, 9 }));
assertEquals(-(3) - 1, kbuf._prefixMatchLength(prefixLength, new byte[] { 9, 9, 9, 9, 9 }));
}
// public static class TestLinearSearch extends TestKeyBufferSearch {
//
// public int search(AbstractKeyBuffer kbuf,byte[]key) {
//
// return kbuf._linearSearch(offset,key);
//
// }
//
// }
//
// public static class TestBinarySearch extends TestKeyBufferSearch {
//
// public int search(AbstractKeyBuffer kbuf,int offset,byte[]key) {
//
// return kbuf._binarySearch(offset,key);
//
// }
//
// }
// /**
// * Performance test to identify the tradeoff point for binary for linear
// * search.
// *
// * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
// * @version $Id$
// */
// public static class PerformanceTest extends TestCase {
//
// public PerformanceTest() {
// }
//
// public PerformanceTest(String name) {
// super(name);
// }
//
// public void testPerformance() {
//
// int ntrials = 50000;
//
// doPerformanceTest(ntrials);
//
// }
//
// Random r = new Random();
//
// /**
// * Generate a set of N random distinct byte[] keys in sorted order using
// * an unsigned byte[] comparison function.
// *
// * @param nkeys The #of keys to generate.
// *
// * @param maxKeyLen The maximum length of a key.
// */
// public byte[][] getRandomKeys(int nkeys, int maxKeyLen) {
//
// // used to ensure distinct keys.
// Set<byte[]> set = new TreeSet<byte[]>(BytesUtil.UnsignedByteArrayComparator.INSTANCE);
//
// byte[][] keys = new byte[nkeys][];
//
// int n = 0;
//
// while( n < nkeys ) {
//
// // random key length in [1:maxKeyLen].
// byte[] key = new byte[r.nextInt(maxKeyLen)+1];
//
// // random data in the key.
// r.nextBytes(key);
//
// if( set.add(key)) {
//
// keys[n++] = key;
//
// }
//
// }
//
// // place into sorted order.
// Arrays.sort(keys,BytesUtil.UnsignedByteArrayComparator.INSTANCE);
//
// return keys;
//
// }
//
// /**
// * Performance test comparing binary vs linear search.
// *
// * @param ntrials
// */
// public void doPerformanceTest(int ntrials) {
//
// /*
// * Note: searching large arrays first since that warms up the code
// * even further and the difference between the linear vs binary
// * algorithms will only show up at small N, which we test last with
// * the "warmest" code.
// */
// //int[] capacity = new int[]{8,16,32,48,64,96,128,256,512,768,1024};
// int[] capacity = new int[]{1024,768,512,256,128,96,64,48,32,24,16,12,8,4};
//
// for( int k = 0; k < capacity.length; k++ ) {
//
// int nkeys = capacity[k];
//
// /*
// * @todo see how performance varies by average key length and
// * consider the key length distribution as well as the key value
// * distribution - the latter is important for interpolated
// * search.
// */
// byte[][] keys = getRandomKeys(nkeys, 20);
//
// {
//
// MutableKeyBuffer kbuf = new MutableKeyBuffer(nkeys, keys);
//
// final int prefixLength = kbuf.getPrefixLength();
//
// // [0:prefixLength-1].
// final int offset = prefixLength == 0 ? 0 : r.nextInt(prefixLength);
//
// long elapsedLinear1 = doTest(true, ntrials, keys, kbuf);
//
// long elapsedBinary1 = doTest(false, ntrials, keys, kbuf);
//
// System.err
// .println(" mutable[]: nkeys="
// + nkeys
// + ", trials="
// + ntrials
// + ", offset="+offset
// + ", elapsedLinear="
// + elapsedLinear1
// + "ns"
// + ", elapsedBinary="
// + elapsedBinary1
// + "ns"
// + (elapsedLinear1 < elapsedBinary1 ? ", linear wins"
// : ", binary wins") + " by "
// + Math.abs(elapsedLinear1 - elapsedBinary1)
// + "ns");
// }
//
// {
//
// ImmutableKeyBuffer kbuf = new ImmutableKeyBuffer(nkeys,keys);
//
// final int prefixLength = kbuf.getPrefixLength();
//
// // [0:prefixLength-1].
// final int offset = prefixLength == 0 ? 0 : r.nextInt(prefixLength);
//
// long elapsedLinear2 = doTest(true, ntrials, keys, kbuf);
//
// long elapsedBinary2 = doTest(false, ntrials, keys, kbuf);
//
// System.err
// .println("immutable[]: nkeys="
// + nkeys
// + ", trials="
// + ntrials
// + ", offset="+offset
// + ", elapsedLinear="
// + elapsedLinear2
// + "ns"
// + ", elapsedBinary="
// + elapsedBinary2
// + "ns"
// + (elapsedLinear2 < elapsedBinary2 ? ", linear wins"
// : ", binary wins") + " by "
// + Math.abs(elapsedLinear2 - elapsedBinary2)
// + "ns");
// }
//
// }
//
// }
//
// /**
// * Time a bunch of searches.
// *
// * @param linear
// * use linear search when true
// * @param ntrials
// * #of searches to perform.
// * @param keys
// * search keys are randomly selected from this array of keys.
// * @param kbuf
// * The key buffer on which the search will be performed.
// *
// * @return the elapsed time aggregated across the searches.
// */
// public long doTest(boolean linear,int ntrials, byte[][] keys, AbstractKeyBuffer kbuf) {
//
// long elapsedNanos = 0;
//
// final int nkeys = kbuf.getKeyCount();
//
// for( int i=0; i<ntrials; i++ ) {
//
// // index of the search key in the key buffer.
// final int index = r.nextInt(nkeys);
//
// byte[] key = keys[ index ];
//
// final int index2;
//
// long beginNanos = System.nanoTime();
//
// if( linear ) {
//
// index2 = kbuf._linearSearch(offset,key);
//
// } else {
//
// index2 = kbuf._binarySearch(offset,key);
//
// }
//
// elapsedNanos += System.nanoTime() - beginNanos;
//
// // make sure the search result is correct.
// if (index != index2) {
//
// fail("Expected to find search key at index=" + index
// + ", not at index=" + index2 + "; searchKey=" + key
// + ", key[index]="
// + BytesUtil.toString(kbuf.getKey(index))
// + ", key[index2]="
// + BytesUtil.toString(kbuf.getKey(index2)));
//
// }
//
// }
//
// return elapsedNanos;
//
// }
//
// }
}