package picard.illumina.parser.readers; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import picard.PicardException; import java.io.File; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.Iterator; public class MMapBackedIteratorFactoryTest { public static File TestDataDir = new File("testdata/picard/illumina/readerTests"); public static File BinFile = new File(TestDataDir, "binary_passing.bin"); public static final int FileLength = 51; //remember that the MMappedBinaryFileReader assumes little endianness public byte[] fileAsBytes(final int start, final int end){ final int [] bInts = { 0x31, 0x22, 0x41, 0x01, 0x45, 0x6E, 0x64, 0x4F, 0x66, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x6F, 0x64, 0x79, 0x50, 0x61, 0x72, 0x74, 0x6F, 0x66, 0x54, 0x68, 0x65, 0x46, 0x69, 0x6C, 0x65, 0x37, 0x37, 0x0A, 0x45, 0x6E, 0x64, 0x43, 0x6F, 0x6D, 0x6D, 0x75, 0x6E, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E }; final int total = end - start + 1; final byte [] bytes = new byte[total]; for(int i = 0; i < total; i++) { bytes[i] = (byte)bInts[start + i]; } return bytes; } //Note these integers are different from the ones in fileAsBytes->bInts (because we're talking about 4 bytes at a time not one) @DataProvider(name="passing_bin_asTdBuffer") public ByteBuffer headerAsByteBuffer(final int headerSize) { final byte [] bytes = fileAsBytes(0, headerSize-1); final ByteBuffer bb = ByteBuffer.allocate(bytes.length); bb.put(bytes); bb.position(0); bb.order(ByteOrder.LITTLE_ENDIAN); return bb; } @DataProvider(name="passing_bin_asTdBuffer") public ByteBuffer fileAsByteBuffer(final int headerSize) { final byte [] bytes = fileAsBytes(headerSize, FileLength-1); final ByteBuffer bb = ByteBuffer.allocate(bytes.length); bb.put(bytes); bb.position(0); bb.order(ByteOrder.LITTLE_ENDIAN); return bb; } abstract class FileTestDef<T> { public final int headerSize; public final BinaryFileIterator bbIter; public final int numElements; public FileTestDef(final int headerSize, final BinaryFileIterator<T> bbIter) { this.headerSize = headerSize; this.bbIter = bbIter; this.numElements = fileAsBytes(headerSize, FileLength-1).length / bbIter.getElementSize(); } public void test() { final ByteBuffer testBuffer = fileAsByteBuffer(headerSize); if(headerSize > 0) { final ByteBuffer headerBuffer = headerAsByteBuffer(headerSize); testHeaderBytes(headerBuffer, bbIter.getHeaderBytes()); } bbIter.assertTotalElementsEqual(numElements); final Iterator<T> testIter = getTestIterator(testBuffer); while(hasNext(testIter, bbIter)) { Assert.assertEquals(testIter.next(), bbIter.next()); } } public abstract Iterator<T> getTestIterator(final ByteBuffer byteBuffer); public boolean hasNext(final Iterator<T> testIter, final Iterator<T> fileIter) { if(testIter.hasNext() && fileIter.hasNext()) return true; if(testIter.hasNext()) { throw new PicardException("Test data (testIter) has more iterations while fileIter does not!"); } if(fileIter.hasNext()) { throw new PicardException("File data (fileIter) has more iterations while testIter does not!"); } return false; } } abstract class NoHeaderTestIter<T> implements Iterator<T> { public final Buffer buf; public NoHeaderTestIter(final Buffer buf) { this.buf = buf; } public boolean hasNext() { return buf.hasRemaining(); } public void remove() { throw new UnsupportedOperationException(); } } @DataProvider(name = "validTestDefs") public Object[][] validTestDefs() { return new Object[][] { { new FileTestDef<Integer>(15, MMapBackedIteratorFactory.getIntegerIterator(15, BinFile)) { @Override public Iterator<Integer> getTestIterator(final ByteBuffer byteBuffer) { final IntBuffer ib = byteBuffer.asIntBuffer(); return new NoHeaderTestIter<Integer>(ib) { @Override public Integer next() { return ib.get(); } }; } } }, { new FileTestDef<Byte>(2, MMapBackedIteratorFactory.getByteIterator(2, BinFile)) { @Override public Iterator<Byte> getTestIterator(final ByteBuffer byteBuffer) { return new NoHeaderTestIter<Byte>(byteBuffer) { @Override public Byte next() { return byteBuffer.get(); } }; } } }, { new FileTestDef<Float>(19, MMapBackedIteratorFactory.getFloatIterator(19, BinFile)) { @Override public Iterator<Float> getTestIterator(final ByteBuffer byteBuffer) { return new NoHeaderTestIter<Float>(byteBuffer) { @Override public Float next() { return byteBuffer.getFloat(); } }; } } } }; } @Test(dataProvider="validTestDefs") public void testValidConfigurations(final FileTestDef ftd) { ftd.test(); } @Test public void onlyHeaderTest() { final BinaryFileIterator<Integer> bbIter = MMapBackedIteratorFactory.getIntegerIterator((int)BinFile.length(), BinFile); Assert.assertEquals(bbIter.getHeaderBytes(), headerAsByteBuffer((int)BinFile.length())); Assert.assertFalse(bbIter.hasNext()); } @Test(expectedExceptions = PicardException.class) public void tooLargeHeaderTest() { final BinaryFileIterator<Integer> bbIter = MMapBackedIteratorFactory.getIntegerIterator(FileLength + 10, BinFile); bbIter.getHeaderBytes(); } @Test(expectedExceptions = PicardException.class) public void negativeHeaderTest() { final BinaryFileIterator<Integer> bbIter = MMapBackedIteratorFactory.getIntegerIterator(-1, BinFile); bbIter.getHeaderBytes(); } @DataProvider(name="invalidFileSizes") public Object [][] invalidFileSizes() { return new Object[][]{ {1, 12}, //should result in two left over bytes {3, 11}, //should result in one left over elements }; } @Test(expectedExceptions = PicardException.class, dataProvider = "invalidFileSizes") public void invalidFileSizeTests(final int headerSize, final int expectedElements) { final BinaryFileIterator<Integer> bbIter = MMapBackedIteratorFactory.getIntegerIterator(headerSize, BinFile); bbIter.assertTotalElementsEqual(expectedElements); } public void testHeaderBytes(final ByteBuffer bb1, final ByteBuffer bb2) { Assert.assertTrue(bb1.equals(bb2), "Header bytes are not equal! " + bb1.toString() + " != " + bb2.toString()); } }