| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.cassandra.index.sasi.utils; |
| |
| import java.io.*; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.ThreadLocalRandom; |
| |
| import org.apache.cassandra.db.marshal.LongType; |
| import org.apache.cassandra.io.util.ChannelProxy; |
| import org.apache.cassandra.io.util.FileUtils; |
| |
| import org.junit.Assert; |
| import org.junit.Test; |
| |
| public class MappedBufferTest |
| { |
| @Test |
| public void testBasicWriteThenRead() throws Exception |
| { |
| long numLongs = 10000; |
| final MappedBuffer buffer = createTestFile(numLongs); |
| |
| Assert.assertEquals(0, buffer.position()); |
| for (long i = 0; i < numLongs; i++) |
| { |
| Assert.assertEquals(i * 8, buffer.position()); |
| Assert.assertEquals(i, buffer.getLong()); |
| } |
| |
| buffer.position(0); |
| for (long i = 0; i < numLongs; i++) |
| { |
| Assert.assertEquals(i, buffer.getLong(i * 8)); |
| Assert.assertEquals(0, buffer.position()); |
| } |
| |
| // read all the numbers as shorts (all numbers fit into four bytes) |
| for (long i = 0; i < Math.min(Integer.MAX_VALUE, numLongs); i++) |
| Assert.assertEquals(i, buffer.getInt((i * 8) + 4)); |
| |
| // read all the numbers as shorts (all numbers fit into two bytes) |
| for (long i = 0; i < Math.min(Short.MAX_VALUE, numLongs); i++) { |
| Assert.assertEquals(i, buffer.getShort((i * 8) + 6)); |
| } |
| |
| // read all the numbers that can be represented as a single byte |
| for (long i = 0; i < 128; i++) |
| Assert.assertEquals(i, buffer.get((i * 8) + 7)); |
| |
| buffer.close(); |
| } |
| |
| @Test |
| public void testDuplicate() throws Exception |
| { |
| long numLongs = 10; |
| final MappedBuffer buffer1 = createTestFile(numLongs); |
| |
| Assert.assertEquals(0, buffer1.getLong()); |
| Assert.assertEquals(1, buffer1.getLong()); |
| |
| final MappedBuffer buffer2 = buffer1.duplicate(); |
| |
| Assert.assertEquals(2, buffer1.getLong()); |
| Assert.assertEquals(2, buffer2.getLong()); |
| |
| buffer2.position(0); |
| Assert.assertEquals(3, buffer1.getLong()); |
| Assert.assertEquals(0, buffer2.getLong()); |
| } |
| |
| @Test |
| public void testLimit() throws Exception |
| { |
| long numLongs = 10; |
| final MappedBuffer buffer1 = createTestFile(numLongs); |
| |
| MappedBuffer buffer2 = buffer1.duplicate().position(16).limit(32); |
| buffer1.position(0).limit(16); |
| List<Long> longs = new ArrayList<>(4); |
| |
| while (buffer1.hasRemaining()) |
| longs.add(buffer1.getLong()); |
| |
| while (buffer2.hasRemaining()) |
| longs.add(buffer2.getLong()); |
| |
| Assert.assertArrayEquals(new Long[]{0L, 1L, 2L, 3L}, longs.toArray()); |
| } |
| |
| @Test(expected = IllegalArgumentException.class) |
| public void testPositionGreaterThanLimit() throws Exception |
| { |
| final MappedBuffer buffer = createTestFile(1); |
| |
| buffer.limit(4); |
| |
| try |
| { |
| buffer.position(buffer.limit() + 1); |
| } |
| finally |
| { |
| buffer.close(); |
| } |
| } |
| |
| @Test(expected = IllegalArgumentException.class) |
| public void testNegativePosition() throws Exception |
| { |
| try (MappedBuffer buffer = createTestFile(1)) |
| { |
| buffer.position(-1); |
| } |
| } |
| |
| @Test(expected = IllegalArgumentException.class) |
| public void testLimitGreaterThanCapacity() throws Exception |
| { |
| try (MappedBuffer buffer = createTestFile(1)) |
| { |
| buffer.limit(buffer.capacity() + 1); |
| } |
| } |
| |
| @Test(expected = IllegalArgumentException.class) |
| public void testLimitLessThanPosition() throws Exception |
| { |
| final MappedBuffer buffer = createTestFile(1); |
| |
| buffer.position(1); |
| |
| try |
| { |
| buffer.limit(0); |
| } |
| finally |
| { |
| buffer.close(); |
| } |
| } |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetRelativeUnderflow() throws Exception |
| { |
| final MappedBuffer buffer = createTestFile(1); |
| |
| buffer.position(buffer.limit()); |
| try |
| { |
| buffer.get(); |
| } |
| finally |
| { |
| buffer.close(); |
| } |
| |
| } |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetAbsoluteGreaterThanCapacity() throws Exception |
| { |
| try (MappedBuffer buffer = createTestFile(1)) |
| { |
| buffer.get(buffer.limit()); |
| } |
| } |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetAbsoluteNegativePosition() throws Exception |
| { |
| try (MappedBuffer buffer = createTestFile(1)) |
| { |
| buffer.get(-1); |
| } |
| } |
| |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetShortRelativeUnderflow() throws Exception |
| { |
| final MappedBuffer buffer = createTestFile(1); |
| |
| buffer.position(buffer.capacity() - 1); |
| try |
| { |
| buffer.getShort(); |
| } |
| finally |
| { |
| buffer.close(); |
| } |
| |
| } |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetShortAbsoluteGreaterThanCapacity() throws Exception |
| { |
| final MappedBuffer buffer = createTestFile(1); |
| |
| Assert.assertEquals(8, buffer.capacity()); |
| try |
| { |
| buffer.getShort(buffer.capacity() - 1); |
| } |
| finally |
| { |
| buffer.close(); |
| } |
| } |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetShortAbsoluteNegativePosition() throws Exception |
| { |
| try (MappedBuffer buffer = createTestFile(1)) |
| { |
| buffer.getShort(-1); |
| } |
| } |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetIntRelativeUnderflow() throws Exception |
| { |
| final MappedBuffer buffer = createTestFile(1); |
| |
| buffer.position(buffer.capacity() - 3); |
| try |
| { |
| buffer.getInt(); |
| } |
| finally |
| { |
| buffer.close(); |
| } |
| } |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetIntAbsoluteGreaterThanCapacity() throws Exception |
| { |
| final MappedBuffer buffer = createTestFile(1); |
| |
| Assert.assertEquals(8, buffer.capacity()); |
| try |
| { |
| buffer.getInt(buffer.capacity() - 3); |
| } |
| finally |
| { |
| buffer.close(); |
| } |
| } |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetIntAbsoluteNegativePosition() throws Exception |
| { |
| try (MappedBuffer buffer = createTestFile(1)) |
| { |
| buffer.getInt(-1); |
| } |
| } |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetLongRelativeUnderflow() throws Exception |
| { |
| final MappedBuffer buffer = createTestFile(1); |
| |
| buffer.position(buffer.capacity() - 7); |
| try |
| { |
| buffer.getLong(); |
| } |
| finally |
| { |
| buffer.close(); |
| } |
| |
| } |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetLongAbsoluteGreaterThanCapacity() throws Exception |
| { |
| final MappedBuffer buffer = createTestFile(1); |
| |
| Assert.assertEquals(8, buffer.capacity()); |
| try |
| { |
| buffer.getLong(buffer.capacity() - 7); |
| } |
| finally |
| { |
| buffer.close(); |
| } |
| } |
| |
| @Test(expected = IndexOutOfBoundsException.class) |
| public void testGetLongAbsoluteNegativePosition() throws Exception |
| { |
| try (MappedBuffer buffer = createTestFile(1)) |
| { |
| buffer.getLong(-1); |
| } |
| } |
| |
| @Test |
| public void testGetPageRegion() throws Exception |
| { |
| ThreadLocalRandom random = ThreadLocalRandom.current(); |
| |
| int numLongs = 1000; |
| int byteSize = 8; |
| int capacity = numLongs * byteSize; |
| try (MappedBuffer buffer = createTestFile(numLongs)) |
| { |
| for (int i = 0; i < 1000; i++) |
| { |
| // offset, length are always aligned on sizeof(long) |
| int offset = random.nextInt(0, 1000 * byteSize - byteSize) & ~(byteSize - 1); |
| int length = Math.min(capacity, random.nextInt(byteSize, capacity - offset) & ~(byteSize - 1)); |
| |
| ByteBuffer region = buffer.getPageRegion(offset, length); |
| for (int j = offset; j < (offset + length); j += 8) |
| Assert.assertEquals(j / 8, region.getLong(j)); |
| } |
| } |
| } |
| |
| @Test (expected = IllegalArgumentException.class) |
| public void testMisalignedRegionAccess() throws Exception |
| { |
| try (MappedBuffer buffer = createTestFile(100, 8, 4, 0)) |
| { |
| buffer.getPageRegion(13, 27); |
| } |
| } |
| |
| @Test |
| public void testSequentialIterationWithPadding() throws Exception |
| { |
| long numValues = 1000; |
| int maxPageBits = 6; // 64 bytes page |
| int[] paddings = new int[] { 0, 3, 5, 7, 9, 11, 13 }; |
| |
| // test different page sizes, with different padding and types |
| for (int numPageBits = 3; numPageBits <= maxPageBits; numPageBits++) |
| { |
| for (int typeSize = 2; typeSize <= 8; typeSize *= 2) |
| { |
| for (int padding : paddings) |
| { |
| try (MappedBuffer buffer = createTestFile(numValues, typeSize, numPageBits, padding)) |
| { |
| long offset = 0; |
| for (long j = 0; j < numValues; j++) |
| { |
| switch (typeSize) |
| { |
| case 2: |
| Assert.assertEquals(j, buffer.getShort(offset)); |
| break; |
| |
| case 4: |
| Assert.assertEquals(j, buffer.getInt(offset)); |
| break; |
| |
| case 8: |
| Assert.assertEquals(j, buffer.getLong(offset)); |
| break; |
| |
| default: |
| throw new AssertionError(); |
| } |
| |
| offset += typeSize + padding; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void testSequentialIteration() throws IOException |
| { |
| long numValues = 1000; |
| for (int typeSize = 2; typeSize <= 8; typeSize *= 2) |
| { |
| try (MappedBuffer buffer = createTestFile(numValues, typeSize, 16, 0)) |
| { |
| for (int j = 0; j < numValues; j++) |
| { |
| Assert.assertEquals(j * typeSize, buffer.position()); |
| |
| switch (typeSize) |
| { |
| case 2: |
| Assert.assertEquals(j, buffer.getShort()); |
| break; |
| |
| case 4: |
| Assert.assertEquals(j, buffer.getInt()); |
| break; |
| |
| case 8: |
| Assert.assertEquals(j, buffer.getLong()); |
| break; |
| |
| default: |
| throw new AssertionError(); |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void testCompareToPage() throws IOException |
| { |
| long numValues = 100; |
| int typeSize = 8; |
| |
| try (MappedBuffer buffer = createTestFile(numValues)) |
| { |
| for (long i = 0; i < numValues * typeSize; i += typeSize) |
| { |
| long value = i / typeSize; |
| Assert.assertEquals(0, buffer.comparePageTo(i, typeSize, LongType.instance, LongType.instance.decompose(value))); |
| } |
| } |
| } |
| |
| @Test |
| public void testOpenWithoutPageBits() throws IOException |
| { |
| File tmp = File.createTempFile("mapped-buffer", "tmp"); |
| tmp.deleteOnExit(); |
| |
| RandomAccessFile file = new RandomAccessFile(tmp, "rw"); |
| |
| long numValues = 1000; |
| for (long i = 0; i < numValues; i++) |
| file.writeLong(i); |
| |
| file.getFD().sync(); |
| |
| try (MappedBuffer buffer = new MappedBuffer(new ChannelProxy(tmp.getAbsolutePath(), file.getChannel()))) |
| { |
| Assert.assertEquals(numValues * 8, buffer.limit()); |
| Assert.assertEquals(numValues * 8, buffer.capacity()); |
| |
| for (long i = 0; i < numValues; i++) |
| { |
| Assert.assertEquals(i * 8, buffer.position()); |
| Assert.assertEquals(i, buffer.getLong()); |
| } |
| } |
| finally |
| { |
| FileUtils.closeQuietly(file); |
| } |
| } |
| |
| @Test(expected = IllegalArgumentException.class) |
| public void testIncorrectPageSize() throws Exception |
| { |
| new MappedBuffer(null, 33); |
| } |
| |
| private MappedBuffer createTestFile(long numCount) throws IOException |
| { |
| return createTestFile(numCount, 8, 16, 0); |
| } |
| |
| private MappedBuffer createTestFile(long numCount, int typeSize, int numPageBits, int padding) throws IOException |
| { |
| final File testFile = File.createTempFile("mapped-buffer-test", "db"); |
| testFile.deleteOnExit(); |
| |
| RandomAccessFile file = new RandomAccessFile(testFile, "rw"); |
| |
| for (long i = 0; i < numCount; i++) |
| { |
| |
| switch (typeSize) |
| { |
| case 1: |
| file.write((byte) i); |
| break; |
| |
| case 2: |
| file.writeShort((short) i); |
| break; |
| |
| case 4: |
| file.writeInt((int) i); |
| break; |
| |
| case 8: |
| // bunch of longs |
| file.writeLong(i); |
| break; |
| |
| default: |
| throw new IllegalArgumentException("unknown byte size: " + typeSize); |
| } |
| |
| for (int j = 0; j < padding; j++) |
| file.write(0); |
| } |
| |
| file.getFD().sync(); |
| |
| try |
| { |
| return new MappedBuffer(new ChannelProxy(testFile.getAbsolutePath(), file.getChannel()), numPageBits); |
| } |
| finally |
| { |
| FileUtils.closeQuietly(file); |
| } |
| } |
| |
| } |