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.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023import static org.junit.Assert.fail;
024
025import java.io.IOException;
026import java.nio.ByteBuffer;
027import java.util.concurrent.ThreadLocalRandom;
028import org.apache.hadoop.hbase.HBaseClassTestRule;
029import org.apache.hadoop.hbase.nio.ByteBuff;
030import org.apache.hadoop.hbase.nio.MultiByteBuff;
031import org.apache.hadoop.hbase.nio.SingleByteBuff;
032import org.apache.hadoop.hbase.testclassification.MiscTests;
033import org.apache.hadoop.hbase.testclassification.SmallTests;
034import org.junit.ClassRule;
035import org.junit.Test;
036import org.junit.experimental.categories.Category;
037
038@Category({ MiscTests.class, SmallTests.class })
039public class TestByteBufferArray {
040
041  @ClassRule
042  public static final HBaseClassTestRule CLASS_RULE =
043    HBaseClassTestRule.forClass(TestByteBufferArray.class);
044
045  private static final ByteBufferAllocator ALLOC = (size) -> ByteBuffer.allocateDirect((int) size);
046
047  @Test
048  public void testAsSubBufferWhenEndOffsetLandInLastBuffer() throws Exception {
049    int capacity = 4 * 1024 * 1024;
050    ByteBufferArray array = new ByteBufferArray(capacity, ALLOC);
051    ByteBuff subBuf = ByteBuff.wrap(array.asSubByteBuffers(0, capacity));
052    subBuf.position(capacity - 1);// Position to the last byte
053    assertTrue(subBuf.hasRemaining());
054    // Read last byte
055    subBuf.get();
056    assertFalse(subBuf.hasRemaining());
057  }
058
059  @Test
060  public void testByteBufferCreation() throws Exception {
061    int capacity = 470 * 1021 * 1023;
062    ByteBufferArray array = new ByteBufferArray(capacity, ALLOC);
063    assertEquals(118, array.buffers.length);
064    for (int i = 0; i < array.buffers.length; i++) {
065      assertEquals(ByteBufferArray.DEFAULT_BUFFER_SIZE, array.buffers[i].capacity());
066    }
067  }
068
069  @Test
070  public void testByteBufferCreation1() throws Exception {
071    long cap = 7 * 1024L * 1024L;
072    int bufferSize = ByteBufferArray.getBufferSize(cap), bufferCount = 25;
073    ByteBufferArray array = new ByteBufferArray(bufferSize, bufferCount, 16, cap, ALLOC);
074    for (int i = 0; i < array.buffers.length; i++) {
075      assertEquals(458752, array.buffers[i].capacity());
076    }
077  }
078
079  private static void fill(ByteBuff buf, byte val) {
080    for (int i = buf.position(); i < buf.limit(); i++) {
081      buf.put(i, val);
082    }
083  }
084
085  private ByteBuff createByteBuff(int len) {
086    assert len >= 0;
087    int pos = len == 0 ? 0 : ThreadLocalRandom.current().nextInt(len);
088    ByteBuff b = ByteBuff.wrap(ByteBuffer.allocate(2 * len));
089    b.position(pos).limit(pos + len);
090    return b;
091  }
092
093  private interface Call {
094    void run() throws IOException;
095  }
096
097  private void expectedAssert(Call r) throws IOException {
098    boolean asserted = true;
099    try {
100      r.run();
101      asserted = false;
102    } catch (AssertionError e) {
103      // Expected
104    }
105    if (!asserted) {
106      fail("Failed to assert expected assertion");
107    }
108  }
109
110  @Test
111  public void testArrayIO() throws IOException {
112    int cap = 9 * 1024 * 1024, bufferSize = ByteBufferArray.getBufferSize(cap);
113    ByteBufferArray array = new ByteBufferArray(cap, ALLOC);
114    testReadAndWrite(array, 0, 512, (byte) 2);
115    testReadAndWrite(array, cap - 512, 512, (byte) 3);
116    testReadAndWrite(array, 4 * 1024 * 1024, 5 * 1024 * 1024, (byte) 4);
117    testReadAndWrite(array, 256, 256, (byte) 5);
118    testReadAndWrite(array, 257, 513, (byte) 6);
119    testReadAndWrite(array, 0, cap, (byte) 7);
120    testReadAndWrite(array, cap, 0, (byte) 8);
121    testReadAndWrite(array, cap - 1, 1, (byte) 9);
122    testReadAndWrite(array, cap - 2, 2, (byte) 10);
123
124    expectedAssert(() -> testReadAndWrite(array, cap - 2, 3, (byte) 11));
125    expectedAssert(() -> testReadAndWrite(array, 0, cap + 1, (byte) 12));
126    expectedAssert(() -> testReadAndWrite(array, 0, -23, (byte) 14));
127    expectedAssert(() -> testReadAndWrite(array, 4096, cap - 4096 + 1, (byte) 16));
128
129    // XXX: These cases were apparently expected to assert but expectedAssert() was
130    // incorrectly implemented as a no-op. Fix these?
131    // expectedAssert(() -> testReadAndWrite(array, cap + 1, 0, (byte) 12));
132    // expectedAssert(() -> testReadAndWrite(array, -1, 0, (byte) 13));
133    // expectedAssert(() -> testReadAndWrite(array, 0, 0, (byte) 15));
134
135    testAsSubByteBuff(array, 0, cap, true);
136    testAsSubByteBuff(array, 0, 0, false);
137    testAsSubByteBuff(array, 0, 1, false);
138    testAsSubByteBuff(array, 0, bufferSize - 1, false);
139    testAsSubByteBuff(array, 0, bufferSize, false);
140    testAsSubByteBuff(array, 0, bufferSize + 1, true);
141    testAsSubByteBuff(array, 0, 2 * bufferSize, true);
142    testAsSubByteBuff(array, 0, 5 * bufferSize, true);
143    testAsSubByteBuff(array, cap - bufferSize - 1, bufferSize, true);
144    testAsSubByteBuff(array, cap - bufferSize, bufferSize, false);
145    testAsSubByteBuff(array, cap - bufferSize, 0, false);
146    testAsSubByteBuff(array, cap - bufferSize, 1, false);
147    testAsSubByteBuff(array, cap - bufferSize, bufferSize - 1, false);
148    testAsSubByteBuff(array, cap - 2 * bufferSize, 2 * bufferSize, true);
149    testAsSubByteBuff(array, cap - 2 * bufferSize, bufferSize + 1, true);
150    testAsSubByteBuff(array, cap - 2 * bufferSize, bufferSize - 1, false);
151    testAsSubByteBuff(array, cap - 2 * bufferSize, 0, false);
152
153    expectedAssert(() -> testAsSubByteBuff(array, 0, cap + 1, false));
154    expectedAssert(() -> testAsSubByteBuff(array, 0, -1, false));
155    expectedAssert(() -> testAsSubByteBuff(array, -1, -1, false));
156    expectedAssert(() -> testAsSubByteBuff(array, cap - bufferSize, bufferSize + 1, false));
157    expectedAssert(() -> testAsSubByteBuff(array, 2 * bufferSize, cap - 2 * bufferSize + 1, false));
158  }
159
160  private void testReadAndWrite(ByteBufferArray array, int off, int dataSize, byte val) {
161    ByteBuff src = createByteBuff(dataSize);
162    int pos = src.position(), lim = src.limit();
163    fill(src, val);
164    assertEquals(src.remaining(), dataSize);
165    try {
166      assertEquals(dataSize, array.write(off, src));
167      assertEquals(0, src.remaining());
168    } finally {
169      src.position(pos).limit(lim);
170    }
171
172    ByteBuff dst = createByteBuff(dataSize);
173    pos = dst.position();
174    lim = dst.limit();
175    try {
176      assertEquals(dataSize, array.read(off, dst));
177      assertEquals(0, dst.remaining());
178    } finally {
179      dst.position(pos).limit(lim);
180    }
181    assertByteBuffEquals(src, dst);
182  }
183
184  private void testAsSubByteBuff(ByteBufferArray array, int off, int len, boolean isMulti) {
185    ByteBuff ret = ByteBuff.wrap(array.asSubByteBuffers(off, len));
186    if (isMulti) {
187      assertTrue(ret instanceof MultiByteBuff);
188    } else {
189      assertTrue(ret instanceof SingleByteBuff);
190    }
191    assertTrue(!ret.hasArray());
192    assertEquals(len, ret.remaining());
193
194    ByteBuff tmp = createByteBuff(len);
195    int pos = tmp.position(), lim = tmp.limit();
196    try {
197      assertEquals(len, array.read(off, tmp));
198      assertEquals(0, tmp.remaining());
199    } finally {
200      tmp.position(pos).limit(lim);
201    }
202
203    assertByteBuffEquals(ret, tmp);
204  }
205
206  private void assertByteBuffEquals(ByteBuff a, ByteBuff b) {
207    assertEquals(a.remaining(), b.remaining());
208    for (int i = a.position(), j = b.position(); i < a.limit(); i++, j++) {
209      assertEquals(a.get(i), b.get(j));
210    }
211  }
212}