/**
*
* 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.hadoop.hbase.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import junit.framework.Test;
import junit.framework.TestCase;
import org.apache.hadoop.hbase.SmallTests;
import org.apache.hadoop.hbase.util.PoolMap.PoolType;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({TestPoolMap.TestRoundRobinPoolType.class, TestPoolMap.TestThreadLocalPoolType.class, TestPoolMap.TestReusablePoolType.class})
@Category(SmallTests.class)
public class TestPoolMap {
public abstract static class TestPoolType extends TestCase {
protected PoolMap<String, String> poolMap;
protected Random random = new Random();
protected static final int POOL_SIZE = 3;
@Override
protected void setUp() throws Exception {
this.poolMap = new PoolMap<String, String>(getPoolType(), POOL_SIZE);
}
protected abstract PoolType getPoolType();
@Override
protected void tearDown() throws Exception {
this.poolMap.clear();
}
protected void runThread(final String randomKey, final String randomValue,
final String expectedValue) throws InterruptedException {
final AtomicBoolean matchFound = new AtomicBoolean(false);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
poolMap.put(randomKey, randomValue);
String actualValue = poolMap.get(randomKey);
matchFound.set(expectedValue == null ? actualValue == null
: expectedValue.equals(actualValue));
}
});
thread.start();
thread.join();
assertTrue(matchFound.get());
}
}
@Category(SmallTests.class)
public static class TestRoundRobinPoolType extends TestPoolType {
@Override
protected PoolType getPoolType() {
return PoolType.RoundRobin;
}
public void testSingleThreadedClient() throws InterruptedException,
ExecutionException {
String randomKey = String.valueOf(random.nextInt());
String randomValue = String.valueOf(random.nextInt());
// As long as the pool is not full, we'll get null back.
// This forces the user to create new values that can be used to populate
// the pool.
runThread(randomKey, randomValue, null);
assertEquals(1, poolMap.size(randomKey));
}
public void testMultiThreadedClients() throws InterruptedException,
ExecutionException {
for (int i = 0; i < POOL_SIZE; i++) {
String randomKey = String.valueOf(random.nextInt());
String randomValue = String.valueOf(random.nextInt());
// As long as the pool is not full, we'll get null back
runThread(randomKey, randomValue, null);
// As long as we use distinct keys, each pool will have one value
assertEquals(1, poolMap.size(randomKey));
}
poolMap.clear();
String randomKey = String.valueOf(random.nextInt());
for (int i = 0; i < POOL_SIZE - 1; i++) {
String randomValue = String.valueOf(random.nextInt());
// As long as the pool is not full, we'll get null back
runThread(randomKey, randomValue, null);
// since we use the same key, the pool size should grow
assertEquals(i + 1, poolMap.size(randomKey));
}
// at the end of the day, there should be as many values as we put
assertEquals(POOL_SIZE - 1, poolMap.size(randomKey));
}
public void testPoolCap() throws InterruptedException, ExecutionException {
String randomKey = String.valueOf(random.nextInt());
List<String> randomValues = new ArrayList<String>();
for (int i = 0; i < POOL_SIZE * 2; i++) {
String randomValue = String.valueOf(random.nextInt());
randomValues.add(randomValue);
if (i < POOL_SIZE - 1) {
// As long as the pool is not full, we'll get null back
runThread(randomKey, randomValue, null);
} else {
// when the pool becomes full, we expect the value we get back to be
// what we put earlier, in round-robin order
runThread(randomKey, randomValue,
randomValues.get((i - POOL_SIZE + 1) % POOL_SIZE));
}
}
assertEquals(POOL_SIZE, poolMap.size(randomKey));
}
}
@Category(SmallTests.class)
public static class TestThreadLocalPoolType extends TestPoolType {
@Override
protected PoolType getPoolType() {
return PoolType.ThreadLocal;
}
public void testSingleThreadedClient() throws InterruptedException,
ExecutionException {
String randomKey = String.valueOf(random.nextInt());
String randomValue = String.valueOf(random.nextInt());
// As long as the pool is not full, we should get back what we put
runThread(randomKey, randomValue, randomValue);
assertEquals(1, poolMap.size(randomKey));
}
public void testMultiThreadedClients() throws InterruptedException,
ExecutionException {
// As long as the pool is not full, we should get back what we put
for (int i = 0; i < POOL_SIZE; i++) {
String randomKey = String.valueOf(random.nextInt());
String randomValue = String.valueOf(random.nextInt());
runThread(randomKey, randomValue, randomValue);
assertEquals(1, poolMap.size(randomKey));
}
String randomKey = String.valueOf(random.nextInt());
for (int i = 0; i < POOL_SIZE; i++) {
String randomValue = String.valueOf(random.nextInt());
runThread(randomKey, randomValue, randomValue);
assertEquals(i + 1, poolMap.size(randomKey));
}
}
public void testPoolCap() throws InterruptedException, ExecutionException {
String randomKey = String.valueOf(random.nextInt());
for (int i = 0; i < POOL_SIZE * 2; i++) {
String randomValue = String.valueOf(random.nextInt());
// as of HBASE-4150, pool limit is no longer used with ThreadLocalPool
runThread(randomKey, randomValue, randomValue);
}
assertEquals(POOL_SIZE * 2, poolMap.size(randomKey));
}
}
@Category(SmallTests.class)
public static class TestReusablePoolType extends TestPoolType {
@Override
protected PoolType getPoolType() {
return PoolType.Reusable;
}
public void testSingleThreadedClient() throws InterruptedException,
ExecutionException {
String randomKey = String.valueOf(random.nextInt());
String randomValue = String.valueOf(random.nextInt());
// As long as we poll values we put, the pool size should remain zero
runThread(randomKey, randomValue, randomValue);
assertEquals(0, poolMap.size(randomKey));
}
public void testMultiThreadedClients() throws InterruptedException,
ExecutionException {
// As long as we poll values we put, the pool size should remain zero
for (int i = 0; i < POOL_SIZE; i++) {
String randomKey = String.valueOf(random.nextInt());
String randomValue = String.valueOf(random.nextInt());
runThread(randomKey, randomValue, randomValue);
assertEquals(0, poolMap.size(randomKey));
}
poolMap.clear();
String randomKey = String.valueOf(random.nextInt());
for (int i = 0; i < POOL_SIZE - 1; i++) {
String randomValue = String.valueOf(random.nextInt());
runThread(randomKey, randomValue, randomValue);
assertEquals(0, poolMap.size(randomKey));
}
assertEquals(0, poolMap.size(randomKey));
}
public void testPoolCap() throws InterruptedException, ExecutionException {
// As long as we poll values we put, the pool size should remain zero
String randomKey = String.valueOf(random.nextInt());
List<String> randomValues = new ArrayList<String>();
for (int i = 0; i < POOL_SIZE * 2; i++) {
String randomValue = String.valueOf(random.nextInt());
randomValues.add(randomValue);
runThread(randomKey, randomValue, randomValue);
}
assertEquals(0, poolMap.size(randomKey));
}
}
}