package voldemort.store.readonly.chunk; import static org.junit.Assert.assertEquals; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import org.junit.Assert; import org.junit.Test; import voldemort.TestUtils; import voldemort.VoldemortException; import voldemort.utils.ByteUtils; import com.google.common.collect.Lists; public class DataFileChunkSetIteratorTest { public class SimpleDataFileChunkSetIterator extends DataFileChunkSetIterator<ByteBuffer> { public SimpleDataFileChunkSetIterator(DataFileChunkSet dataFileChunkSet, boolean coalesceCollided) { super(dataFileChunkSet, coalesceCollided); } @Override public ByteBuffer next() { if(!hasNext()) throw new VoldemortException("Reached the end"); try { if(coalesceCollided) { // Read a short ByteBuffer numKeysBytes = ByteBuffer.allocate(ByteUtils.SIZE_OF_SHORT); getCurrentChunk().read(numKeysBytes, getCurrentOffsetInChunk()); short numKeys = numKeysBytes.getShort(0); // Read all the collided values ByteBuffer values = ByteBuffer.allocate(numKeys * ByteUtils.SIZE_OF_INT); getCurrentChunk().read(values, getCurrentOffsetInChunk() + ByteUtils.SIZE_OF_SHORT); // update the offset updateOffset(getCurrentOffsetInChunk() + ByteUtils.SIZE_OF_SHORT + (numKeys * ByteUtils.SIZE_OF_INT)); return values; } else { // Read a value ByteBuffer value = ByteBuffer.allocate(ByteUtils.SIZE_OF_INT); getCurrentChunk().read(value, getCurrentOffsetInChunk()); updateOffset(getCurrentOffsetInChunk() + ByteUtils.SIZE_OF_INT); return value; } } catch(IOException e) { throw new VoldemortException(e); } } } public DataFileChunkSet generateDataFileChunkSet(int numberOfChunks, int numberOfEmptyChunks, int numberOfEntriesPerChunk, int numberOfEntriesPerCollision) throws IOException { Assert.assertEquals(numberOfEntriesPerChunk % numberOfEntriesPerCollision, 0); List<DataFileChunk> dataFiles = Lists.newArrayList(); List<Integer> dataFileSizes = Lists.newArrayList(); File tempFolder = TestUtils.createTempDir(); int currentEntry = 0; List<Boolean> isEmpty = Lists.newArrayList(); for(int chunkId = 0; chunkId < numberOfChunks; chunkId++) { isEmpty.add(false); } for(int chunkId = 0; chunkId < numberOfEmptyChunks; chunkId++) { isEmpty.add(true); } Collections.shuffle(isEmpty); for(int chunkId = 0; chunkId < (numberOfChunks + numberOfEmptyChunks); chunkId++) { boolean empty = isEmpty.get(chunkId); // Generate a file File chunkFile = new File(tempFolder, Integer.toString(chunkId)); chunkFile.createNewFile(); if(!empty) { FileOutputStream stream = new FileOutputStream(chunkFile); for(int entryId = 0; entryId < numberOfEntriesPerChunk / numberOfEntriesPerCollision; entryId++) { ByteBuffer buffer = ByteBuffer.allocate(ByteUtils.SIZE_OF_SHORT + numberOfEntriesPerCollision * ByteUtils.SIZE_OF_INT); buffer.putShort((short) numberOfEntriesPerCollision); for(int entryInCollision = 0; entryInCollision < numberOfEntriesPerCollision; entryInCollision++) { buffer.putInt(currentEntry); currentEntry++; } stream.write(buffer.array()); } stream.flush(); stream.close(); } dataFiles.add(new LocalDataFileChunk(new FileInputStream(chunkFile).getChannel())); dataFileSizes.add((int) chunkFile.length()); } DataFileChunkSet chunkSet = new DataFileChunkSet(dataFiles, dataFileSizes); return chunkSet; } @Test public void testIterator() throws IOException { // 1) Test with empty chunk files testIterator(0, 100, 1); // 2) Test with one chunk file testIterator(1, 100, 1); // 3) Test with two chunk files testIterator(2, 100, 1); // 4) Test with two chunk files testIterator(10, 100, 1); // 5) Test with collisions testIterator(1, 100, 2); // 6) Test with collisions testIterator(2, 100, 2); // 7) Test with collisions testIterator(10, 100, 5); // 8) Test with collided entries + empty chunk files testIteratorWithCollidedEntry(0, 100, 1); // 9) Test with collided entries testIteratorWithCollidedEntry(1, 100, 1); // 10) Test with collided entries testIteratorWithCollidedEntry(1, 100, 2); // 11) Test with collided entries testIteratorWithCollidedEntry(1, 100, 5); // 12) Test with collided entries testIteratorWithCollidedEntry(10, 100, 1); // 13) Test with collided entries testIteratorWithCollidedEntry(10, 100, 2); // 14) Test with collided entries testIteratorWithCollidedEntry(10, 100, 5); } public void testIterator(int numChunks, int numEntriesPerChunks, int numEntriesPerCollision) throws IOException { // 1) Test with empty chunk files SimpleDataFileChunkSetIterator iterator = new SimpleDataFileChunkSetIterator(generateDataFileChunkSet(numChunks, 10, numEntriesPerChunks, numEntriesPerCollision), false); if(numChunks * numEntriesPerChunks == 0) { assertEquals(iterator.hasNext(), false); } else { for(int entry = 0; entry < numChunks * numEntriesPerChunks; entry++) { assertEquals(iterator.hasNext(), true); ByteBuffer buffer = iterator.next(); assertEquals(buffer.getInt(0), entry); } } } public void testIteratorWithCollidedEntry(int numChunks, int numEntriesPerChunks, int numEntriesPerCollision) throws IOException { // 1) Test with empty chunk files SimpleDataFileChunkSetIterator iterator = new SimpleDataFileChunkSetIterator(generateDataFileChunkSet(numChunks, 10, numEntriesPerChunks, numEntriesPerCollision), true); if(numChunks * numEntriesPerChunks == 0) { assertEquals(iterator.hasNext(), false); } else { int entry = 0; for(int collidedEntry = 0; collidedEntry < numChunks * (numEntriesPerChunks / numEntriesPerCollision); collidedEntry++) { assertEquals(iterator.hasNext(), true); ByteBuffer buffer = iterator.next(); // Read number assertEquals(buffer.array().length, numEntriesPerCollision * ByteUtils.SIZE_OF_INT); for(int index = 0; index < numEntriesPerCollision; index++) { assertEquals(buffer.getInt(ByteUtils.SIZE_OF_INT * index), entry++); } } } } }