/**
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 Jul 1, 2011
*/
package com.bigdata.htree.raba;
import java.io.IOException;
import junit.framework.TestCase2;
import com.bigdata.btree.raba.IRaba;
import com.bigdata.btree.raba.ReadOnlyValuesRaba;
import com.bigdata.htree.HTree;
import com.bigdata.io.DataInputBuffer;
/**
* Test suite for {@link MutableValuesBuffer}.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class TestMutableValueBuffer extends TestCase2 {
/**
*
*/
public TestMutableValueBuffer() {
}
/**
* @param name
*/
public TestMutableValueBuffer(String name) {
super(name);
}
/**
* Unit tests for constructor accepting only the capacity of the buffer.
*/
public void test_ctor_capacity_correct_rejection() {
// Capacity must be positive and a (positive) power of two (not 2^0).
try {
new MutableValueBuffer(0);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
try {
new MutableValueBuffer(-1);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
try {
new MutableValueBuffer(1);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
try {
new MutableValueBuffer(3);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
try {
new MutableValueBuffer(17);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
}
/**
* These are legal invocations.
*/
public void test_ctor_capacity_validInvocations() {
new MutableValueBuffer(2);
new MutableValueBuffer(4);
new MutableValueBuffer(8);
new MutableValueBuffer(16);
}
/**
* Check the post-conditions for a legal invocation of the constructor.
*/
public void test_ctor_capacity_postConditions() {
final int m = 2;
final MutableValueBuffer t = new MutableValueBuffer(m);
assertEquals(0, t.size());
assertEquals(m, t.capacity());
assertNotNull(t.values);
assertEquals(m, t.values.length);
for (int i = 0; i < m; i++) {
assertNull(t.values[i]);
}
}
public void test_ctor_wrap_correct_rejection() {
try {
new MutableValueBuffer(0/* nvalues */, (byte[][]) null);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
// not a valid power of 2.
try {
new MutableValueBuffer(0/* nvalues */, new byte[1][]);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
// not a valid power of 2.
try {
new MutableValueBuffer(0/* nvalues */, new byte[3][]);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
// size is negative
try {
new MutableValueBuffer(-1/* nvalues */, new byte[2][]);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
// size is too large
try {
new MutableValueBuffer(3/* nvalues */, new byte[2][]);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
}
/**
* Unit test constructor wrapping the caller's data. For this constructor,
* the new instance should use the same references and the capacity should
* be the dimension of the source byte[][].
*/
public void test_ctor_wrap_postConditions() {
final byte[] k0 = null;
final byte[] k1 = new byte[] { 1 };
final byte[] k2 = new byte[] { 2 };
final byte[] k3 = null;
final byte[][] values = new byte[][] { k0, k1, k2, k3 };
final MutableValueBuffer buf = new MutableValueBuffer(2/* nvalues */, values);
assertEquals(values.length, buf.capacity());
assertEquals(2, buf.size());
// same data
assertEquals(k0, buf.values[0]);
assertEquals(k1, buf.values[1]);
assertEquals(k2, buf.values[2]);
assertEquals(k3, buf.values[3]);
// same references.
assertTrue(k0 == buf.values[0]);
assertTrue(k1 == buf.values[1]);
assertTrue(k2 == buf.values[2]);
assertTrue(k3 == buf.values[3]);
// same byte[][] reference.
assertTrue(values == buf.values);
}
public void test_ctor_copy_correct_rejection() {
try {
new MutableValueBuffer(null);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
}
/**
* Unit test the copy constructor.
*/
public void test_ctor_copy() {
final byte[] k0 = null;
final byte[] k1 = new byte[] { 1 };
final byte[] k2 = new byte[] { 2 };
final byte[] k3 = null;
final byte[][] values = new byte[][] { k0, k1, k2, k3 };
final MutableValueBuffer src = new MutableValueBuffer(2/* nvalues */, values);
final MutableValueBuffer buf = new MutableValueBuffer(src);
assertEquals(values.length, buf.capacity());
assertEquals(2, buf.size());
// same data
assertEquals(k0, buf.values[0]);
assertEquals(k1, buf.values[1]);
assertEquals(k2, buf.values[2]);
assertEquals(k3, buf.values[3]);
// same references.
assertTrue(k0 == buf.values[0]);
assertTrue(k1 == buf.values[1]);
assertTrue(k2 == buf.values[2]);
assertTrue(k3 == buf.values[3]);
// NOT the same byte[][] reference.
assertTrue(values != buf.values);
}
public void test_ctor_raba_with_explicit_capacity_correct_rejection() {
final byte[] k0 = null;
final byte[] k1 = new byte[] { 1 };
final byte[] k2 = new byte[] { 2 };
final byte[] k3 = null;
final byte[][] values = new byte[][] { k0, k1, k2, k3 };
final IRaba src = new ReadOnlyValuesRaba(values);
// raba may not be null
try {
new MutableValueBuffer(2/* capacity */, (IRaba) null/* src */);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
// capacity must be 2^n where n is positive.
try {
new MutableValueBuffer(3/* capacity */, (IRaba) src);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
// capacity must not be LT the capacity of the raba.
try {
new MutableValueBuffer(2/* capacity */, (IRaba) src);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
// ignored.
}
}
/**
* Unit test the constructor variant which accepts an {@link IRaba} and an
* explicitly given capacity.
*/
public void test_ctor_raba_with_explicit_capacity() {
final byte[] k0 = new byte[] { 0 };
final byte[] k1 = new byte[] { 1 };
final byte[] k2 = new byte[] { 2 };
final byte[] k3 = new byte[] { 3 };
final byte[][] values = new byte[][] { k0, k1, k2, k3 };
/*
* Note: The ReadOnlyValuesRaba assumes that the 1st size values have
* valid data. Thus it is not really suited to sparse random access
* pattern of the HTree values and values.
*/
final IRaba src = new ReadOnlyValuesRaba(4/* size */, values);
final MutableValueBuffer buf = new MutableValueBuffer(8/* capacity */, src);
assertEquals(8, buf.capacity()); // NB: As given!
assertEquals(4, buf.size());
// same data
assertEquals(k0, buf.values[0]);
assertEquals(k1, buf.values[1]);
assertEquals(k2, buf.values[2]);
assertEquals(k3, buf.values[3]);
assertNull(buf.values[4]);
assertNull(buf.values[5]);
assertNull(buf.values[6]);
assertNull(buf.values[7]);
// NOT the same byte[][] reference.
assertTrue(values != buf.values);
}
/**
* The values of the {@link HTree} do not have any of the requirements of the
* B+Tree values. The values in a bucket page may contain duplicates and
* <code>null</code>s and are not searchable (only scannable, and then only
* within a logical buddy bucket). Therefore this class reports
* <code>false</code> for {@link IRaba#isvalues()}.
*/
public void test_isvalues() {
// Note: Must be a power of 2 for an HTree.
final int m = 4;
final MutableValueBuffer kbuf = new MutableValueBuffer(m);
assertFalse(kbuf.isKeys());
}
/**
* The various methods "add()" methods are not supported because they do not
* specify the index of the tuple and we need to have that since the tuples
* are not dense and the bucket page is divided into buddy bucket regions
* which must be managed separately.
*
* @throws IOException
*/
public void test_add_not_supported() throws IOException {
// Note: Must be a power of 2 for an HTree.
final int m = 4;
final MutableValueBuffer kbuf = new MutableValueBuffer(m);
try {
kbuf.add(new byte[] { 1, 2 });
fail("Expecting: " + UnsupportedOperationException.class);
} catch (UnsupportedOperationException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
try {
kbuf.add(new byte[] { 1, 2 }, 0/* off */, 1/* len */);
fail("Expecting: " + UnsupportedOperationException.class);
} catch (UnsupportedOperationException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
try {
kbuf.add(new DataInputBuffer(new byte[2]), 1/* len */);
fail("Expecting: " + UnsupportedOperationException.class);
} catch (UnsupportedOperationException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
}
/**
* {@link IRaba#search(byte[])} is not supported because it must be done
* within a buddy hash bucket boundary. Since the values are neither ordered
* nor dense, search must use a simple scan. Also note that there can be
* duplicate values within a buddy hash table.
*/
public void test_search_not_supported() {
// Note: Must be a power of 2 for an HTree.
final int m = 4;
final MutableValueBuffer kbuf = new MutableValueBuffer(m);
try {
kbuf.search(new byte[] { 1, 2 });
fail("Expecting: " + UnsupportedOperationException.class);
} catch (UnsupportedOperationException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
}
/**
* Test for key mutation operations (adding, removing, etc).
*/
public void test_mutation() {
// Note: Must be a power of 2 for an HTree.
final int m = 4;
final MutableValueBuffer kbuf = new MutableValueBuffer(m);
if (log.isInfoEnabled())
log.info(kbuf.toString());
assertEquals("values.length", m, kbuf.values.length);
assertEquals("maxvalues", m, kbuf.capacity());
assertEquals("nvalues", 0, kbuf.nvalues);
assertNull(kbuf.values[0]);
final byte[] k0 = new byte[] { 1, 2, 0 };
final byte[] k1 = new byte[] { 1, 2, 1 };
final byte[] k2 = new byte[] { 1, 2, 2 };
final byte[] k3 = new byte[] { 1, 2, 3 };
kbuf.set(0/* index */, k0);
if (log.isInfoEnabled())
log.info(kbuf.toString());
assertEquals("nvalues", 1, kbuf.nvalues);
assertFalse(kbuf.isFull());
assertEquals(k0, kbuf.values[0]);
kbuf.set(2/* index */, k2); // Note leaves a "hole" at index 1.
if (log.isInfoEnabled())
log.info(kbuf.toString());
assertEquals("nvalues", 2, kbuf.nvalues);
assertFalse(kbuf.isFull());
assertEquals(k0, kbuf.values[0]);
assertEquals(k2, kbuf.values[2]);
kbuf.set(3/* index */, k3);
if (log.isInfoEnabled())
log.info(kbuf.toString());
assertEquals("nvalues", 3, kbuf.nvalues);
assertFalse(kbuf.isFull());
assertEquals(k0, kbuf.values[0]);
assertEquals(k2, kbuf.values[2]);
assertEquals(k3, kbuf.values[3]);
kbuf.set(1/* index */, k1);
if (log.isInfoEnabled())
log.info(kbuf.toString());
assertEquals("nvalues", 4, kbuf.nvalues);
assertTrue(kbuf.isFull());
assertEquals(k0, kbuf.values[0]);
assertEquals(k1, kbuf.values[1]);
assertEquals(k2, kbuf.values[2]);
assertEquals(k3, kbuf.values[3]);
// remove the 1st key, leaving 3 values.
assertEquals("nvalues", 3, kbuf.remove(0));
if (log.isInfoEnabled())
log.info(kbuf.toString());
assertEquals("nvalues", 3, kbuf.nvalues);
assertFalse(kbuf.isFull());
assertEquals(k1, kbuf.values[0]);
assertEquals(k2, kbuf.values[1]);
assertEquals(k3, kbuf.values[2]);
assertNull(kbuf.values[3]);
// remove the last key, leaving 2 values.
assertEquals("nvalues", 2, kbuf.remove(2));
if (log.isInfoEnabled())
log.info(kbuf.toString());
assertEquals("nvalues", 2, kbuf.nvalues);
assertFalse(kbuf.isFull());
assertEquals(k1, kbuf.values[0]);
assertEquals(k2, kbuf.values[1]);
assertNull(kbuf.values[2]);
assertNull(kbuf.values[3]);
// insert a key in the 1st position (3 values in the buffer).
kbuf.insert(0, k1);
if (log.isInfoEnabled())
log.info(kbuf.toString());
assertEquals("nvalues", 3, kbuf.nvalues);
assertFalse(kbuf.isFull());
assertEquals(k1, kbuf.values[0]); // note: duplicate key.
assertEquals(k1, kbuf.values[1]);
assertEquals(k2, kbuf.values[2]);
assertNull(kbuf.values[3]);
// set a key in the last position (buffer is full again).
kbuf.set(3, k2);
if (log.isInfoEnabled())
log.info(kbuf.toString());
assertEquals("nvalues", 4, kbuf.nvalues);
assertTrue(kbuf.isFull());
assertEquals(k1, kbuf.values[0]);
assertEquals(k1, kbuf.values[1]);
assertEquals(k2, kbuf.values[2]);
assertEquals(k2, kbuf.values[3]); // note: another duplicate key.
}
}