001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.util;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023import static org.junit.jupiter.api.Assertions.fail;
024
025import java.io.IOException;
026import java.nio.ByteBuffer;
027import java.util.concurrent.ThreadLocalRandom;
028import org.apache.hadoop.hbase.nio.ByteBuff;
029import org.apache.hadoop.hbase.nio.MultiByteBuff;
030import org.apache.hadoop.hbase.nio.SingleByteBuff;
031import org.apache.hadoop.hbase.testclassification.MiscTests;
032import org.apache.hadoop.hbase.testclassification.SmallTests;
033import org.junit.jupiter.api.Tag;
034import org.junit.jupiter.api.Test;
035
036@Tag(MiscTests.TAG)
037@Tag(SmallTests.TAG)
038public class TestByteBufferArray {
039
040  private static final ByteBufferAllocator ALLOC = (size) -> ByteBuffer.allocateDirect((int) size);
041
042  @Test
043  public void testAsSubBufferWhenEndOffsetLandInLastBuffer() throws Exception {
044    int capacity = 4 * 1024 * 1024;
045    ByteBufferArray array = new ByteBufferArray(capacity, ALLOC);
046    ByteBuff subBuf = ByteBuff.wrap(array.asSubByteBuffers(0, capacity));
047    subBuf.position(capacity - 1);// Position to the last byte
048    assertTrue(subBuf.hasRemaining());
049    // Read last byte
050    subBuf.get();
051    assertFalse(subBuf.hasRemaining());
052  }
053
054  @Test
055  public void testByteBufferCreation() throws Exception {
056    int capacity = 470 * 1021 * 1023;
057    ByteBufferArray array = new ByteBufferArray(capacity, ALLOC);
058    assertEquals(118, array.buffers.length);
059    for (int i = 0; i < array.buffers.length; i++) {
060      assertEquals(ByteBufferArray.DEFAULT_BUFFER_SIZE, array.buffers[i].capacity());
061    }
062  }
063
064  @Test
065  public void testByteBufferCreation1() throws Exception {
066    long cap = 7 * 1024L * 1024L;
067    int bufferSize = ByteBufferArray.getBufferSize(cap), bufferCount = 25;
068    ByteBufferArray array = new ByteBufferArray(bufferSize, bufferCount, 16, cap, ALLOC);
069    for (int i = 0; i < array.buffers.length; i++) {
070      assertEquals(458752, array.buffers[i].capacity());
071    }
072  }
073
074  private static void fill(ByteBuff buf, byte val) {
075    for (int i = buf.position(); i < buf.limit(); i++) {
076      buf.put(i, val);
077    }
078  }
079
080  private ByteBuff createByteBuff(int len) {
081    assert len >= 0;
082    int pos = len == 0 ? 0 : ThreadLocalRandom.current().nextInt(len);
083    ByteBuff b = ByteBuff.wrap(ByteBuffer.allocate(2 * len));
084    b.position(pos).limit(pos + len);
085    return b;
086  }
087
088  private interface Call {
089    void run() throws IOException;
090  }
091
092  private void expectedAssert(Call r) throws IOException {
093    boolean asserted = true;
094    try {
095      r.run();
096      asserted = false;
097    } catch (AssertionError e) {
098      // Expected
099    }
100    if (!asserted) {
101      fail("Failed to assert expected assertion");
102    }
103  }
104
105  @Test
106  public void testArrayIO() throws IOException {
107    int cap = 9 * 1024 * 1024, bufferSize = ByteBufferArray.getBufferSize(cap);
108    ByteBufferArray array = new ByteBufferArray(cap, ALLOC);
109    testReadAndWrite(array, 0, 512, (byte) 2);
110    testReadAndWrite(array, cap - 512, 512, (byte) 3);
111    testReadAndWrite(array, 4 * 1024 * 1024, 5 * 1024 * 1024, (byte) 4);
112    testReadAndWrite(array, 256, 256, (byte) 5);
113    testReadAndWrite(array, 257, 513, (byte) 6);
114    testReadAndWrite(array, 0, cap, (byte) 7);
115    testReadAndWrite(array, cap, 0, (byte) 8);
116    testReadAndWrite(array, cap - 1, 1, (byte) 9);
117    testReadAndWrite(array, cap - 2, 2, (byte) 10);
118
119    expectedAssert(() -> testReadAndWrite(array, cap - 2, 3, (byte) 11));
120    expectedAssert(() -> testReadAndWrite(array, 0, cap + 1, (byte) 12));
121    expectedAssert(() -> testReadAndWrite(array, 0, -23, (byte) 14));
122    expectedAssert(() -> testReadAndWrite(array, 4096, cap - 4096 + 1, (byte) 16));
123
124    // XXX: These cases were apparently expected to assert but expectedAssert() was
125    // incorrectly implemented as a no-op. Fix these?
126    // expectedAssert(() -> testReadAndWrite(array, cap + 1, 0, (byte) 12));
127    // expectedAssert(() -> testReadAndWrite(array, -1, 0, (byte) 13));
128    // expectedAssert(() -> testReadAndWrite(array, 0, 0, (byte) 15));
129
130    testAsSubByteBuff(array, 0, cap, true);
131    testAsSubByteBuff(array, 0, 0, false);
132    testAsSubByteBuff(array, 0, 1, false);
133    testAsSubByteBuff(array, 0, bufferSize - 1, false);
134    testAsSubByteBuff(array, 0, bufferSize, false);
135    testAsSubByteBuff(array, 0, bufferSize + 1, true);
136    testAsSubByteBuff(array, 0, 2 * bufferSize, true);
137    testAsSubByteBuff(array, 0, 5 * bufferSize, true);
138    testAsSubByteBuff(array, cap - bufferSize - 1, bufferSize, true);
139    testAsSubByteBuff(array, cap - bufferSize, bufferSize, false);
140    testAsSubByteBuff(array, cap - bufferSize, 0, false);
141    testAsSubByteBuff(array, cap - bufferSize, 1, false);
142    testAsSubByteBuff(array, cap - bufferSize, bufferSize - 1, false);
143    testAsSubByteBuff(array, cap - 2 * bufferSize, 2 * bufferSize, true);
144    testAsSubByteBuff(array, cap - 2 * bufferSize, bufferSize + 1, true);
145    testAsSubByteBuff(array, cap - 2 * bufferSize, bufferSize - 1, false);
146    testAsSubByteBuff(array, cap - 2 * bufferSize, 0, false);
147
148    expectedAssert(() -> testAsSubByteBuff(array, 0, cap + 1, false));
149    expectedAssert(() -> testAsSubByteBuff(array, 0, -1, false));
150    expectedAssert(() -> testAsSubByteBuff(array, -1, -1, false));
151    expectedAssert(() -> testAsSubByteBuff(array, cap - bufferSize, bufferSize + 1, false));
152    expectedAssert(() -> testAsSubByteBuff(array, 2 * bufferSize, cap - 2 * bufferSize + 1, false));
153  }
154
155  private void testReadAndWrite(ByteBufferArray array, int off, int dataSize, byte val) {
156    ByteBuff src = createByteBuff(dataSize);
157    int pos = src.position(), lim = src.limit();
158    fill(src, val);
159    assertEquals(src.remaining(), dataSize);
160    try {
161      assertEquals(dataSize, array.write(off, src));
162      assertEquals(0, src.remaining());
163    } finally {
164      src.position(pos).limit(lim);
165    }
166
167    ByteBuff dst = createByteBuff(dataSize);
168    pos = dst.position();
169    lim = dst.limit();
170    try {
171      assertEquals(dataSize, array.read(off, dst));
172      assertEquals(0, dst.remaining());
173    } finally {
174      dst.position(pos).limit(lim);
175    }
176    assertByteBuffEquals(src, dst);
177  }
178
179  private void testAsSubByteBuff(ByteBufferArray array, int off, int len, boolean isMulti) {
180    ByteBuff ret = ByteBuff.wrap(array.asSubByteBuffers(off, len));
181    if (isMulti) {
182      assertTrue(ret instanceof MultiByteBuff);
183    } else {
184      assertTrue(ret instanceof SingleByteBuff);
185    }
186    assertTrue(!ret.hasArray());
187    assertEquals(len, ret.remaining());
188
189    ByteBuff tmp = createByteBuff(len);
190    int pos = tmp.position(), lim = tmp.limit();
191    try {
192      assertEquals(len, array.read(off, tmp));
193      assertEquals(0, tmp.remaining());
194    } finally {
195      tmp.position(pos).limit(lim);
196    }
197
198    assertByteBuffEquals(ret, tmp);
199  }
200
201  private void assertByteBuffEquals(ByteBuff a, ByteBuff b) {
202    assertEquals(a.remaining(), b.remaining());
203    for (int i = a.position(), j = b.position(); i < a.limit(); i++, j++) {
204      assertEquals(a.get(i), b.get(j));
205    }
206  }
207}