/*
* Copyright Terracotta, Inc.
*
* Licensed 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.ehcache.impl.internal.concurrent;
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
/**
* @author Ludovic Orban
*/
public class ConcurrentHashMapITest {
private static final int ENTRIES = 10000;
@Test
public void testRandomValuesWithObjects() {
ConcurrentHashMap<Object, KeyHolder<Object>> map = new ConcurrentHashMap<Object, KeyHolder<Object>>();
for(int i = 0; i < ENTRIES; i++) {
final Object o = new Object();
map.put(o, new KeyHolder<Object>(o));
}
assertThings(map);
}
@Test
public void testRandomValuesWithComparable() {
ConcurrentHashMap<Comparable<?>, KeyHolder<Object>> map = new ConcurrentHashMap<Comparable<?>, KeyHolder<Object>>();
for(int i = 0; i < ENTRIES; i++) {
final EvilComparableKey o = new EvilComparableKey(UUID.randomUUID().toString());
map.put(o, new KeyHolder<Object>(o));
}
assertThings(map);
}
@Test
public void testRandomValues() {
ConcurrentHashMap<Object, KeyHolder<Object>> map = new ConcurrentHashMap<Object, KeyHolder<Object>>();
final long seed = System.nanoTime();
System.out.println("SEED: " + seed);
final Random random = new Random(seed);
for(int i = 0; i < ENTRIES; i++) {
final Object o;
switch(i % 4) {
case 0:
final int hashCode = random.nextInt();
o = new Object() {
@Override
public int hashCode() {
return hashCode;
}
};
break;
case 1:
o = new EvilKey(Integer.toString(i));
break;
default:
o = new EvilComparableKey(Integer.toString(i));
}
assertThat(map.put(o, new KeyHolder<Object>(o)) == null, is(true));
}
for (Object o : map.keySet()) {
assertThat(o.toString(), map.containsKey(o), is(true));
assertThat(o.toString(), map.get(o), notNullValue());
}
assertThings(map);
}
@Test
public void testRandomValuesWithCollisions() {
ConcurrentHashMap<Object, KeyHolder<Object>> map = new ConcurrentHashMap<Object, KeyHolder<Object>>();
for(int i = 0; i < ENTRIES; i++) {
final EvilKey o = new EvilKey(UUID.randomUUID().toString());
map.put(o, new KeyHolder<Object>(o));
}
assertThings(map);
}
@Test
public void testActuallyWorks() throws InterruptedException {
final long top = 100000000;
final String key = "counter";
final ConcurrentHashMap<String, Long> map = new ConcurrentHashMap<String, Long>();
map.put(key, 0L);
final Runnable runnable = new Runnable() {
@Override
public void run() {
for(Long val = map.get(key); val < top && map.replace(key, val, val + 1); val = map.get(key));
}
};
Thread[] threads = new Thread[Runtime.getRuntime().availableProcessors() * 2];
for (int i = 0, threadsLength = threads.length; i < threadsLength; ) {
threads[i] = new Thread(runnable);
threads[i].setName("Mutation thread #" + ++i);
threads[i - 1].start();
}
for (Thread thread : threads) {
thread.join();
}
assertThat(map.get(key), is(top));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void assertThings(final ConcurrentHashMap<?, ?> map) {
assertThat(map.size(), is(ENTRIES));
for(int i = 0; i < 100; i ++) {
final HashSet randomValues = new HashSet(getRandomValues(map, ENTRIES));
assertThat(randomValues.size(), is(ENTRIES));
for (Object randomValue : randomValues) {
assertThat(randomValue, instanceOf(KeyHolder.class));
final Object key = ((KeyHolder)randomValue).key;
assertThat("Missing " + key, map.containsKey(key), is(true));
}
}
}
private static List<?> getRandomValues(Map<?, ?> map, int amount) {
List<?> values = new ArrayList<Object>(map.values());
Collections.shuffle(values);
return values.subList(0, amount);
}
static class KeyHolder<K> {
final K key;
KeyHolder(final K key) {
this.key = key;
}
}
static class EvilKey {
final String value;
EvilKey(final String value) {
this.value = value;
}
@Override
public int hashCode() {
return this.value.hashCode() & 1;
}
@Override
public boolean equals(final Object obj) {
return obj != null && obj.getClass() == this.getClass() && ((EvilKey)obj).value.equals(value);
}
@Override
public String toString() {
return this.getClass().getSimpleName() + " { " + value + " }";
}
}
static class EvilComparableKey extends EvilKey implements Comparable<EvilComparableKey> {
EvilComparableKey(final String value) {
super(value);
}
@Override
public int compareTo(final EvilComparableKey o) {
return value.compareTo(o != null ? o.value : null);
}
}
}