/*
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 May 7, 2009
*/
package com.bigdata.service.ndx;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import junit.framework.TestCase2;
import com.bigdata.btree.IndexMetadata;
import com.bigdata.mdi.IMetadataIndex;
import com.bigdata.mdi.MetadataIndex;
import com.bigdata.mdi.PartitionLocator;
import com.bigdata.rawstore.SimpleMemoryRawStore;
import com.bigdata.service.Split;
/**
* Unit tests for {@link ISplitter}.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*
* @todo review the costs of the validation logic used by
* {@link AbstractSplitter}.
*/
public class TestSplitter extends TestCase2 {
/**
*
*/
public TestSplitter() {
}
/**
* @param name
*/
public TestSplitter(String name) {
super(name);
}
/**
* API error checking for args.
*/
public void test_splitter_correctRejection() {
final ISplitter splitter = new AbstractSplitter() {
@Override
protected IMetadataIndex getMetadataIndex(long ts) {
throw new UnsupportedOperationException();
}
};
final long timestampOk = 0L;
// fromIndex is negative.
try {
splitter.splitKeys(timestampOk, -1/* fromIndex */, 1/* toIndex */,
new byte[][] {}/* keys */);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
// fromIndex GTE length (also toIndex GT length)
try {
final byte[][] keys = new byte[][] {
new byte[] {},
new byte[] {}
};
splitter.splitKeys(timestampOk, 2/* fromIndex */, 3/* toIndex */,
keys);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
// fromIndex GTE toIndex
try {
final byte[][] keys = new byte[][] {
new byte[] {},
new byte[] {}
};
splitter.splitKeys(timestampOk, 1/* fromIndex */, 1/* toIndex */,
keys);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
// keys is null
try {
final byte[][] keys = null;
splitter.splitKeys(timestampOk, 0/* fromIndex */, 1/* toIndex */,
keys);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
// keys has null element.
try {
final byte[][] keys = new byte[][] {
null
};
splitter.splitKeys(timestampOk, 0/* fromIndex */, 1/* toIndex */,
keys);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
}
/**
* Test computing the correct splits with a single key and a single index
* partition.
*
* @todo unit test when the MDI does not have any entries (this is an error
* which is checked by {@link MetadataIndex#find(byte[])}).
*/
public void test_splitter_correctSplits_01() {
// use one data service (one UUID).
final UUID dataServiceUUID = UUID.randomUUID();
final MetadataIndex mdi = MetadataIndex.create(
new SimpleMemoryRawStore(), UUID.randomUUID(),
new IndexMetadata("test-ndx", UUID.randomUUID()));
final PartitionLocator loc1 = new PartitionLocator(mdi
.incrementAndGetNextPartitionId(), dataServiceUUID,//
new byte[] {}, // leftSeparatorKey
null// rightSeparatorKey
);
mdi.insert(loc1.getLeftSeparatorKey(), loc1);
final ISplitter splitter = new AbstractSplitter() {
@Override
protected IMetadataIndex getMetadataIndex(long ts) {
return mdi;
}
};
final long ts = 0L;
final byte[][] keys = new byte[][] {
new byte[]{1}
};
assertSplits(new Split[] {//
new Split(loc1, 0, 1)//
},//
splitter.splitKeys(ts, 0/* fromIndex */, 1/* toIndex */, keys)
.iterator());
}
/**
* Test with more than one key in the same index partition.
*/
public void test_splitter_correctSplits_02() {
// use one data service (one UUID).
final UUID dataServiceUUID = UUID.randomUUID();
final MetadataIndex mdi = MetadataIndex.create(
new SimpleMemoryRawStore(), UUID.randomUUID(),
new IndexMetadata("test-ndx", UUID.randomUUID()));
final PartitionLocator loc1 = new PartitionLocator(mdi
.incrementAndGetNextPartitionId(), dataServiceUUID,//
new byte[] {}, // leftSeparatorKey
null// rightSeparatorKey
);
mdi.insert(loc1.getLeftSeparatorKey(), loc1);
final ISplitter splitter = new AbstractSplitter() {
@Override
protected IMetadataIndex getMetadataIndex(long ts) {
return mdi;
}
};
final long ts = 0L;
final byte[][] keys = new byte[][] {
new byte[]{1},
new byte[]{3}
};
assertSplits(new Split[] {//
new Split(loc1, 0, 1)//
},//
splitter.splitKeys(ts, 0/* fromIndex */, 1/* toIndex */, keys)
.iterator());
}
/**
* Test with one key into each of two index partitions. The separator key
* between the index partitions is also used as the key that goes into the
* 2nd index partition so this tests right on the fence post.
*/
public void test_splitter_correctSplits_03() {
// use one data service (one UUID).
final UUID dataServiceUUID = UUID.randomUUID();
final MetadataIndex mdi = MetadataIndex.create(
new SimpleMemoryRawStore(), UUID.randomUUID(),
new IndexMetadata("test-ndx", UUID.randomUUID()));
final PartitionLocator loc1 = new PartitionLocator(mdi
.incrementAndGetNextPartitionId(), dataServiceUUID,//
new byte[] {}, // leftSeparatorKey
new byte[] {2} // rightSeparator
);
final PartitionLocator loc2 = new PartitionLocator(mdi
.incrementAndGetNextPartitionId(), dataServiceUUID,//
new byte[] {2}, // leftSeparatorKey
null// rightSeparatorKey
);
mdi.insert(loc1.getLeftSeparatorKey(), loc1);
mdi.insert(loc2.getLeftSeparatorKey(), loc2);
final ISplitter splitter = new AbstractSplitter() {
@Override
protected IMetadataIndex getMetadataIndex(long ts) {
return mdi;
}
};
final long ts = 0L;
final byte[][] keys = new byte[][] {
new byte[]{1},
new byte[]{2}
};
// verify with just the 1st key.
assertSplits(new Split[] {//
new Split(loc1, 0/*fromIndex*/, 1/*toIndex*/),//
// new Split(loc2, 1/*fromIndex*/, 2/*toIndex*/)//
},//
splitter.splitKeys(ts, 0/* fromIndex */, 1/* toIndex */, keys)
.iterator());
// verify with just the 2nd key.
assertSplits(new Split[] {//
// new Split(loc1, 0/*fromIndex*/, 1/*toIndex*/),//
new Split(loc2, 1/*fromIndex*/, 2/*toIndex*/)//
},//
splitter.splitKeys(ts, 1/* fromIndex */, 2/* toIndex */, keys)
.iterator());
// verify with both keys.
assertSplits(new Split[] {//
new Split(loc1, 0/*fromIndex*/, 1/*toIndex*/),//
new Split(loc2, 1/*fromIndex*/, 2/*toIndex*/)//
},//
splitter.splitKeys(ts, 0/* fromIndex */, 2/* toIndex */, keys)
.iterator());
}
/**
* Correct detection when the keys are not fully ordered.
*
* @todo we need to test two code paths. One where the rightSeparator is
* bound and one where it is null (done). This means that we have to
* test with more than one index partition.
*/
public void test_splitter_keysOutOfOrder() {
// use one data service (one UUID).
final UUID dataServiceUUID = UUID.randomUUID();
final MetadataIndex mdi = MetadataIndex.create(
new SimpleMemoryRawStore(), UUID.randomUUID(),
new IndexMetadata("test-ndx", UUID.randomUUID()));
final PartitionLocator loc1 = new PartitionLocator(mdi
.incrementAndGetNextPartitionId(), dataServiceUUID,//
new byte[] {}, // leftSeparatorKey
null// rightSeparatorKey
);
mdi.insert(loc1.getLeftSeparatorKey(), loc1);
final ISplitter splitter = new AbstractSplitter() {
@Override
protected IMetadataIndex getMetadataIndex(long ts) {
return mdi;
}
};
final long ts = 0L;
// keys are out of order.
final byte[][] keys = new byte[][] {
new byte[] { 1 },//
new byte[] { 3 }, //
new byte[] { 2 },
};
try {
splitter.splitKeys(ts, 0/* fromIndex */, keys.length/* toIndex */,
keys);
fail("Expecting: " + IllegalArgumentException.class);
} catch (IllegalArgumentException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
}
/**
* Recognizing duplicate keys, which are OK as long as the do not violate
* the key ordering. This situation arises with asynchronous writes when
* duplicates are not filtered out.
*
* @todo write test.
*/
public void test_splitter_duplicateKeys() {
// fail("write tests");
}
/**
* Verifies the {@link PartitionLocator}, fromIndex, and toIndex on each
* {@link Split} and the order of the {@link Split}s.
*
* @param expected
* @param actual
*/
protected void assertSplits(final Split[] expected,
final Iterator<Split> actual) {
for (Split eSplit : expected) {
assertTrue("Actual did not produce enough splits.", actual
.hasNext());
final Split aSplit = actual.next();
assertEquals(eSplit.pmd.getPartitionId(), aSplit.pmd
.getPartitionId());
assertEquals(eSplit.pmd.getLeftSeparatorKey(), aSplit.pmd
.getLeftSeparatorKey());
assertEquals(eSplit.pmd.getRightSeparatorKey(), aSplit.pmd
.getRightSeparatorKey());
assertEquals(eSplit.fromIndex, aSplit.fromIndex);
assertEquals(eSplit.toIndex, aSplit.toIndex);
}
// should be exhausted.
assertFalse(actual.hasNext());
}
}