blob: 7ffebf166b46856b66d172b97e17d855474dac03 [file] [log] [blame]
/*
* 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);
}
}
}