/** * diqube: Distributed Query Base. * * Copyright (C) 2015 Bastian Gloeckle * * This file is part of diqube. * * diqube is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.diqube.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.util.ArrayList; import java.util.List; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; /** * * @author Bastian Gloeckle */ public class BigByteBufferTest { private List<Runnable> cleanupMethods = new ArrayList<>(); @AfterMethod public void cleanup() { for (Runnable cleanupRun : cleanupMethods) cleanupRun.run(); cleanupMethods.clear(); } @Test public void singleShardTest() throws IOException { // GIVEN try (BigByteBuffer buf = new BigByteBuffer(new byte[] { 1, 2, 3, 4, 5 })) { // WHEN THEN Assert.assertEquals(1, buf.get(0)); Assert.assertEquals(5, buf.get(4)); byte[] tmp = new byte[2]; buf.get(0, tmp, 0, 2); Assert.assertEquals(new byte[] { 1, 2 }, tmp); tmp = new byte[3]; tmp[0] = 0; buf.get(0, tmp, 1, 2); Assert.assertEquals(new byte[] { 0, 1, 2 }, tmp); tmp = new byte[3]; buf.get(2, tmp, 0, 3); Assert.assertEquals(new byte[] { 3, 4, 5 }, tmp); tmp = new byte[5]; buf.get(0, tmp, 0, 5); Assert.assertEquals(new byte[] { 1, 2, 3, 4, 5 }, tmp); tmp = new byte[1]; buf.get(4, tmp, 0, 1); Assert.assertEquals(new byte[] { 5 }, tmp); } } @Test(expectedExceptions = ArrayIndexOutOfBoundsException.class) public void outOfBoundsTest() throws IOException { // GIVEN try (BigByteBuffer buf = new BigByteBuffer(new byte[] { 1 })) { // WHEN THEN buf.get(1); } } @Test(expectedExceptions = ArrayIndexOutOfBoundsException.class) public void negativeTest() throws IOException { // GIVEN try (BigByteBuffer buf = new BigByteBuffer(new byte[] { 1, 2 })) { // WHEN THEN buf.get(-1); } } @Test(expectedExceptions = ArrayIndexOutOfBoundsException.class) public void wrongTargetIndexTest() throws IOException { // GIVEN try (BigByteBuffer buf = new BigByteBuffer(new byte[] { 1, 2 })) { // WHEN THEN byte[] tmp = new byte[2]; buf.get(0, tmp, 1, 2); } } @Test(expectedExceptions = ArrayIndexOutOfBoundsException.class) public void negativeLengthTest() throws IOException { // GIVEN try (BigByteBuffer buf = new BigByteBuffer(new byte[] { 1, 2 })) { // WHEN THEN byte[] tmp = new byte[1]; buf.get(0, tmp, 1, -1); } } @Test public void twoShardTest() throws IOException { // GIVEN ByteBuffer buf1 = ByteBuffer.wrap(new byte[] { 1, 2, 3, 4, 5 }); ByteBuffer buf2 = ByteBuffer.wrap(new byte[] { 11, 12, 13, 14, 15 }); try (BigByteBuffer buf = new BigByteBuffer(new ByteBuffer[] { buf1, buf2 })) { // WHEN THEN Assert.assertEquals(1, buf.get(0)); Assert.assertEquals(5, buf.get(4)); Assert.assertEquals(11, buf.get(5)); Assert.assertEquals(15, buf.get(9)); byte[] tmp = new byte[5]; buf.get(0, tmp, 0, 5); Assert.assertEquals(new byte[] { 1, 2, 3, 4, 5 }, tmp); tmp = new byte[5]; buf.get(5, tmp, 0, 5); Assert.assertEquals(new byte[] { 11, 12, 13, 14, 15 }, tmp); tmp = new byte[2]; buf.get(4, tmp, 0, 2); Assert.assertEquals(new byte[] { 5, 11 }, tmp); tmp = new byte[10]; buf.get(0, tmp, 0, 10); Assert.assertEquals(new byte[] { 1, 2, 3, 4, 5, 11, 12, 13, 14, 15 }, tmp); tmp = new byte[9]; buf.get(1, tmp, 0, 9); Assert.assertEquals(new byte[] { 2, 3, 4, 5, 11, 12, 13, 14, 15 }, tmp); tmp = new byte[11]; tmp[0] = 0; buf.get(0, tmp, 1, 10); Assert.assertEquals(new byte[] { 0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15 }, tmp); } } @Test public void filePerfectMappingTest() throws IOException { // GIVEN File tmpFile = createTempFile(); FileOutputStream fos = new FileOutputStream(tmpFile); fos.write(new byte[] { 1, 2, 3, 4, 5, 11, 12, 13, 14, 15 }); fos.flush(); fos.close(); try (RandomAccessFile f = new RandomAccessFile(tmpFile, "r")) { try (BigByteBuffer buf = new BigByteBuffer(f.getChannel(), MapMode.READ_ONLY, b -> b.load(), 5 // 5 per shard, // maps perfect on // 10 length )) { // WHEN THEN Assert.assertEquals(1, buf.get(0)); Assert.assertEquals(5, buf.get(4)); Assert.assertEquals(11, buf.get(5)); Assert.assertEquals(15, buf.get(9)); byte[] tmp = new byte[5]; buf.get(0, tmp, 0, 5); Assert.assertEquals(new byte[] { 1, 2, 3, 4, 5 }, tmp); tmp = new byte[5]; buf.get(5, tmp, 0, 5); Assert.assertEquals(new byte[] { 11, 12, 13, 14, 15 }, tmp); tmp = new byte[2]; buf.get(4, tmp, 0, 2); Assert.assertEquals(new byte[] { 5, 11 }, tmp); tmp = new byte[10]; buf.get(0, tmp, 0, 10); Assert.assertEquals(new byte[] { 1, 2, 3, 4, 5, 11, 12, 13, 14, 15 }, tmp); tmp = new byte[9]; buf.get(1, tmp, 0, 9); Assert.assertEquals(new byte[] { 2, 3, 4, 5, 11, 12, 13, 14, 15 }, tmp); tmp = new byte[11]; tmp[0] = 0; buf.get(0, tmp, 1, 10); Assert.assertEquals(new byte[] { 0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15 }, tmp); } } } @Test public void fileUnperfectMappingTest() throws IOException { // GIVEN File tmpFile = createTempFile(); FileOutputStream fos = new FileOutputStream(tmpFile); fos.write(new byte[] { 1, 2, 3, 4, 5, 11, 12, 13, 14, 15 }); fos.flush(); fos.close(); try (RandomAccessFile f = new RandomAccessFile(tmpFile, "r")) { try (BigByteBuffer buf = new BigByteBuffer(f.getChannel(), MapMode.READ_ONLY, b -> b.load(), 4 // 4 per shard )) { // WHEN THEN Assert.assertEquals(1, buf.get(0)); Assert.assertEquals(4, buf.get(3)); Assert.assertEquals(5, buf.get(4)); Assert.assertEquals(13, buf.get(7)); Assert.assertEquals(14, buf.get(8)); Assert.assertEquals(15, buf.get(9)); byte[] tmp = new byte[5]; buf.get(0, tmp, 0, 5); Assert.assertEquals(new byte[] { 1, 2, 3, 4, 5 }, tmp); tmp = new byte[5]; buf.get(5, tmp, 0, 5); Assert.assertEquals(new byte[] { 11, 12, 13, 14, 15 }, tmp); tmp = new byte[2]; buf.get(4, tmp, 0, 2); Assert.assertEquals(new byte[] { 5, 11 }, tmp); tmp = new byte[10]; buf.get(0, tmp, 0, 10); Assert.assertEquals(new byte[] { 1, 2, 3, 4, 5, 11, 12, 13, 14, 15 }, tmp); tmp = new byte[9]; buf.get(1, tmp, 0, 9); Assert.assertEquals(new byte[] { 2, 3, 4, 5, 11, 12, 13, 14, 15 }, tmp); tmp = new byte[11]; tmp[0] = 0; buf.get(0, tmp, 1, 10); Assert.assertEquals(new byte[] { 0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15 }, tmp); tmp = new byte[4]; int readCount = buf.get(7, tmp, 0, 4); Assert.assertEquals(new byte[] { 13, 14, 15, 0 }, tmp); Assert.assertEquals(3, readCount); tmp = new byte[4]; readCount = buf.get(10, tmp, 0, 4); Assert.assertEquals(-1, readCount); } } } @Test(expectedExceptions = ArrayIndexOutOfBoundsException.class) public void fileUnperfectMappingExceptionTest() throws IOException { // GIVEN File tmpFile = createTempFile(); FileOutputStream fos = new FileOutputStream(tmpFile); fos.write(new byte[] { 1, 2, 3, 4, 5, 11, 12, 13, 14, 15 }); fos.flush(); fos.close(); try (RandomAccessFile f = new RandomAccessFile(tmpFile, "r")) { try (BigByteBuffer buf = new BigByteBuffer(f.getChannel(), MapMode.READ_ONLY, b -> b.load(), 4 // 4 per shard )) { // WHEN THEN byte[] tmp = new byte[4]; buf.get(11, tmp, 0, 4); } } } @Test public void bigFileSimulationTest() throws IOException { FileChannel mockedChannel = Mockito.mock(FileChannel.class); MappedByteBuffer internalMockedByteBuf = Mockito.mock(MappedByteBuffer.class); long totalSize = Integer.MAX_VALUE * 100L; // use a file size that is larger than an int. Mockito.when(mockedChannel.size()).thenReturn(totalSize); Mockito.when(mockedChannel.map(Mockito.any(), Mockito.anyLong(), Mockito.anyLong())) .thenAnswer(new Answer<MappedByteBuffer>() { @Override public MappedByteBuffer answer(InvocationOnMock invocation) throws Throwable { MapMode mapMode = (MapMode) invocation.getArguments()[0]; Long sourcePosition = (Long) invocation.getArguments()[1]; Long size = (Long) invocation.getArguments()[2]; Assert.assertEquals(mapMode, MapMode.READ_ONLY, "Expected correct map mode."); Assert.assertNotNull(sourcePosition, "Position should not be null"); Assert.assertNotNull(size, "Size should not be null"); Assert.assertTrue(sourcePosition >= 0, "Expected position to be positive, but was " + sourcePosition); Assert.assertTrue(size > 0, "Expected size to be positive, but was " + size); return internalMockedByteBuf; } }); try (BigByteBuffer buf = new BigByteBuffer(mockedChannel, MapMode.READ_ONLY, null)) { // expected: no exception. // Note that we do not test to read from that BigByteBuffer here, since we cannot mock MappedByteBuffer nicely, // since it has final methods. But the other methods test reading from BigByteBuffers with multiple internal // MappedByteBuffers, so we should be fine. } } private File createTempFile() throws IOException { File res = File.createTempFile(this.getClass().getSimpleName(), "tmp"); cleanupMethods.add(new Runnable() { @Override public void run() { res.delete(); } }); return res; } }