content: /*
   * 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.IOException;
  import java.io.RandomAccessFile;
  import java.nio.ByteBuffer;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.ThreadLocalRandom;
  
  import org.junit.Assert;
  import org.junit.Test;
  
  import org.apache.cassandra.db.marshal.LongType;
  import org.apache.cassandra.io.util.ChannelProxy;
  import org.apache.cassandra.io.util.File;
  import org.apache.cassandra.io.util.FileUtils;
  
  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 = FileUtils.createTempFile("mapped-buffer", "tmp");
          tmp.deleteOnExit();
  
          RandomAccessFile file = new RandomAccessFile(tmp.toJavaIOFile(), "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, 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 = FileUtils.createTempFile("mapped-buffer-test", "db");
          testFile.deleteOnExit();
  
          RandomAccessFile file = new RandomAccessFile(testFile.toJavaIOFile(), "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, file.getChannel()), numPageBits);
          }
          finally
          {
              FileUtils.closeQuietly(file);
          }
      }
  
  }
  