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.io.compress;
  
  import java.io.*;
  import java.nio.ByteBuffer;
  import java.nio.MappedByteBuffer;
  import java.nio.channels.FileChannel;
  import java.nio.file.StandardOpenOption;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Random;
  
  import com.google.common.io.Files;
  import org.apache.cassandra.io.util.File;
  import static org.junit.Assert.*;
  import org.junit.Assert;
  import org.junit.Test;
  
  import org.apache.cassandra.io.util.FileUtils;
  import org.apache.cassandra.io.util.RandomAccessReader;
  import org.apache.cassandra.utils.ByteBufferUtil;
  
  public class CompressorTest
  {
      ICompressor compressor;
  
      ICompressor[] compressors = new ICompressor[] {
              LZ4Compressor.create(Collections.<String, String>emptyMap()),
              DeflateCompressor.create(Collections.<String, String>emptyMap()),
              SnappyCompressor.create(Collections.<String, String>emptyMap()),
              ZstdCompressor.create(Collections.emptyMap()),
              NoopCompressor.create(Collections.emptyMap())
      };
  
      @Test
      public void testAllCompressors() throws IOException
      {
          for (ICompressor compressor : compressors)
          {
              this.compressor = compressor;
  
              testEmptyArray();
              testLongArray();
              testShortArray();
              testMappedFile();
          }
      }
  
      public void testArrayUncompress(byte[] data, int off, int len) throws IOException
      {
          final int inOffset = 2;
          ByteBuffer src = makeBB(len + inOffset);
          src.position(inOffset);
          src.put(data, off, len);
          src.flip().position(inOffset);
  
          final int outOffset = 3;
          final ByteBuffer compressed = makeBB(outOffset + compressor.initialCompressedBufferLength(len));
          fillBBWithRandom(compressed);
          compressed.position(outOffset);
  
          compressor.compress(src, compressed);
          compressed.flip().position(outOffset);
  
          final int restoreOffset = 5;
          final byte[] restored = new byte[restoreOffset + len];
          new Random().nextBytes(restored);
  
          // need byte[] representation which direct buffers don't have
          byte[] compressedBytes = new byte[compressed.capacity()];
          ByteBufferUtil.copyBytes(compressed, outOffset, compressedBytes, outOffset, compressed.limit() - outOffset);
  
          final int decompressedLength = compressor.uncompress(compressedBytes, outOffset, compressed.remaining(), restored, restoreOffset);
  
          assertEquals(decompressedLength, len);
          assertArrayEquals(Arrays.copyOfRange(data, off, off + len),
                            Arrays.copyOfRange(restored, restoreOffset, restoreOffset + decompressedLength));
      }
  
      public void testArrayUncompress(byte[] data) throws IOException
      {
          testArrayUncompress(data, 0, data.length);
      }
  
      public void testEmptyArray() throws IOException
      {
          testArrayUncompress(new byte[0]);
      }
  
      public void testShortArray() throws UnsupportedEncodingException, IOException
      {
          testArrayUncompress("Cassandra".getBytes("UTF-8"), 1, 7);
      }
  
      public void testLongArray() throws UnsupportedEncodingException, IOException
      {
          byte[] data = new byte[1 << 20];
          testArrayUncompress(data, 13, 1 << 19);
          new Random(0).nextBytes(data);
          testArrayUncompress(data, 13, 1 << 19);
      }
  
      public void testMappedFile() throws IOException
      {
          byte[] data = new byte[1 << 20];
          new Random().nextBytes(data);
          ByteBuffer src = makeBB(data.length);
          src.put(data);
          src.flip();
  
          // create a temp file
          File temp = FileUtils.createTempFile("tempfile", ".tmp");
          temp.deleteOnExit();
  
          // Prepend some random bytes to the output and compress
          final int outOffset = 3;
          byte[] garbage = new byte[outOffset + compressor.initialCompressedBufferLength(data.length)];
          new Random().nextBytes(garbage);
          ByteBuffer dest = makeBB(outOffset + compressor.initialCompressedBufferLength(data.length));
          dest.put(garbage);
          dest.clear();
          dest.position(outOffset);
  
          compressor.compress(src, dest);
          int compressedLength = dest.position() - outOffset;
  
          FileChannel channel = FileChannel.open(temp.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE);
          dest.clear();
          channel.write(dest);
  
          MappedByteBuffer mappedData = Files.map(temp.toJavaIOFile());
          ByteBuffer result = makeBB(data.length + 100);
          mappedData.position(outOffset).limit(outOffset + compressedLength);
  
          compressor.uncompress(mappedData, result);
          channel.close();
          result.flip();
  
          Assert.assertEquals(data.length, result.limit());
          for (int i = 0; i < result.limit(); i++)
          {
              Assert.assertEquals("Decompression mismatch at byte "+i, data[i], result.get());
          }
      }
  
      @Test
      public void testLZ4ByteBuffers() throws IOException
      {
          compressor = LZ4Compressor.create(Collections.<String, String>emptyMap());
          testByteBuffers();
      }
  
      @Test
      public void testDeflateByteBuffers() throws IOException
      {
          compressor = DeflateCompressor.create(Collections.<String, String>emptyMap());
          testByteBuffers();
      }
  
      @Test
      public void testSnappyByteBuffers() throws IOException
      {
          compressor = SnappyCompressor.create(Collections.<String, String>emptyMap());
          testByteBuffers();
      }
  
      @Test
      public void testZstdByteBuffers() throws IOException
      {
          compressor = ZstdCompressor.create(Collections.<String, String>emptyMap());
          testByteBuffers();
      }
  
      @Test
      public void testNoopByteBuffers() throws IOException
      {
          compressor = NoopCompressor.create(Collections.emptyMap());
          testByteBuffers();
      }
  
      private void testByteBuffers() throws IOException
      {
          assert compressor.supports(BufferType.OFF_HEAP);
          assert compressor.supports(compressor.preferredBufferType());
  
          for (BufferType in: BufferType.values())
              if (compressor.supports(in))
                  for (BufferType comp: BufferType.values())
                      if (compressor.supports(comp))
                          for (BufferType out: BufferType.values())
                              if (compressor.supports(out))
                                  testByteBuffers(in, comp, out);
      }
  
      private void testByteBuffers(BufferType typeIn, BufferType typeComp, BufferType typeOut) throws IOException
      {
          try
          {
              int n = RandomAccessReader.DEFAULT_BUFFER_SIZE;
              byte[] srcData = new byte[n];
              new Random().nextBytes(srcData);
  
              final int inOffset = 2;
              ByteBuffer src = typeIn.allocate(inOffset + n + inOffset);
              src.position(inOffset);
              src.put(srcData, 0, n);
              src.flip().position(inOffset);
  
              int outOffset = 5;
              ByteBuffer compressed = typeComp.allocate(outOffset + compressor.initialCompressedBufferLength(srcData.length) + outOffset);
              byte[] garbage = new byte[compressed.capacity()];
              new Random().nextBytes(garbage);
              compressed.put(garbage);
              compressed.position(outOffset).limit(compressed.capacity() - outOffset);
  
              compressor.compress(src, compressed);
              assertEquals(inOffset + n, src.position());
              assertEquals(inOffset + n, src.limit());
              assertEquals(compressed.capacity() - outOffset, compressed.limit());
              compressed.flip().position(outOffset);
              int len = compressed.remaining();
  
              ByteBuffer result = typeOut.allocate(inOffset + n + inOffset);
              result.position(inOffset).limit(result.capacity() - inOffset);
              compressor.uncompress(compressed, result);
              assertEquals(outOffset + len, compressed.position());
              assertEquals(outOffset + len, compressed.limit());
              assertEquals(result.capacity() - inOffset, result.limit());
  
              int decompressed = result.position() - inOffset;
              assert decompressed == n : "Failed uncompressed size";
              for (int i = 0; i < n; ++i)
                  assert srcData[i] == result.get(inOffset + i) : "Failed comparison on index: " + i;
          }
          catch (Throwable e)
          {
              throw new AssertionError("Failed testing compressor " + compressor.getClass().getSimpleName() + " with buffer types in:" + typeIn + " compressed:" + typeComp + " out:" + typeOut, e);
          }
      }
  
      private ByteBuffer makeBB(int size)
      {
          return compressor.preferredBufferType().allocate(size);
      }
  
      private void fillBBWithRandom(ByteBuffer dest)
      {
          byte[] random = new byte[dest.capacity()];
          new Random().nextBytes(random);
          dest.clear();
          dest.put(random);
      }
  
  }
  