/* * * 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.bookkeeper.util.collections; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; import org.apache.bookkeeper.util.collections.ConcurrentLongLongHashMap.LongLongFunction; import org.junit.Test; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public class ConcurrentLongLongHashMapTest { @Test public void testConstructor() { try { new ConcurrentLongLongHashMap(0); fail("should have thrown exception"); } catch (IllegalArgumentException e) { // ok } try { new ConcurrentLongLongHashMap(16, 0); fail("should have thrown exception"); } catch (IllegalArgumentException e) { // ok } try { new ConcurrentLongLongHashMap(4, 8); fail("should have thrown exception"); } catch (IllegalArgumentException e) { // ok } } @Test public void simpleInsertions() { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(16); assertTrue(map.isEmpty()); assertEquals(map.put(1, 11), -1); assertFalse(map.isEmpty()); assertEquals(map.put(2, 22), -1); assertEquals(map.put(3, 33), -1); assertEquals(map.size(), 3); assertEquals(map.get(1), 11); assertEquals(map.size(), 3); assertEquals(map.remove(1), 11); assertEquals(map.size(), 2); assertEquals(map.get(1), -1); assertEquals(map.get(5), -1); assertEquals(map.size(), 2); assertEquals(map.put(1, 11), -1); assertEquals(map.size(), 3); assertEquals(map.put(1, 111), 11); assertEquals(map.size(), 3); } @Test public void testRemove() { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(); assertTrue(map.isEmpty()); assertEquals(map.put(1, 11), -1); assertFalse(map.isEmpty()); assertFalse(map.remove(0, 0)); assertFalse(map.remove(1, 111)); assertFalse(map.isEmpty()); assertTrue(map.remove(1, 11)); assertTrue(map.isEmpty()); } @Test public void testNegativeUsedBucketCount() { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(16, 1); map.put(0, 0); assertEquals(1, map.getUsedBucketCount()); map.put(0, 1); assertEquals(1, map.getUsedBucketCount()); map.remove(0); assertEquals(0, map.getUsedBucketCount()); map.remove(0); assertEquals(0, map.getUsedBucketCount()); } @Test public void testRehashing() { int n = 16; ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(n / 2, 1); assertEquals(map.capacity(), n); assertEquals(map.size(), 0); for (int i = 0; i < n; i++) { map.put(i, i); } assertEquals(map.capacity(), 2 * n); assertEquals(map.size(), n); } @Test public void testRehashingWithDeletes() { int n = 16; ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(n / 2, 1); assertEquals(map.capacity(), n); assertEquals(map.size(), 0); for (int i = 0; i < n / 2; i++) { map.put(i, i); } for (int i = 0; i < n / 2; i++) { map.remove(i); } for (int i = n; i < (2 * n); i++) { map.put(i, i); } assertEquals(map.capacity(), 2 * n); assertEquals(map.size(), n); } @Test public void concurrentInsertions() throws Throwable { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(); ExecutorService executor = Executors.newCachedThreadPool(); final int nThreads = 16; final int N = 100_000; long value = 55; List<Future<?>> futures = new ArrayList<>(); for (int i = 0; i < nThreads; i++) { final int threadIdx = i; futures.add(executor.submit(() -> { Random random = new Random(); for (int j = 0; j < N; j++) { long key = Math.abs(random.nextLong()); // Ensure keys are uniques key -= key % (threadIdx + 1); map.put(key, value); } })); } for (Future<?> future : futures) { future.get(); } assertEquals(map.size(), N * nThreads); executor.shutdown(); } @Test public void concurrentInsertionsAndReads() throws Throwable { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(); ExecutorService executor = Executors.newCachedThreadPool(); final int nThreads = 16; final int N = 100_000; final long value = 55; List<Future<?>> futures = new ArrayList<>(); for (int i = 0; i < nThreads; i++) { final int threadIdx = i; futures.add(executor.submit(() -> { Random random = new Random(); for (int j = 0; j < N; j++) { long key = Math.abs(random.nextLong()); // Ensure keys are uniques key -= key % (threadIdx + 1); map.put(key, value); } })); } for (Future<?> future : futures) { future.get(); } assertEquals(map.size(), N * nThreads); executor.shutdown(); } @Test public void testIteration() { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(); assertEquals(map.keys(), Collections.emptyList()); assertEquals(map.values(), Collections.emptyList()); map.put(0, 0); assertEquals(map.keys(), Lists.newArrayList(0l)); assertEquals(map.values(), Lists.newArrayList(0l)); map.remove(0); assertEquals(map.keys(), Collections.emptyList()); assertEquals(map.values(), Collections.emptyList()); map.put(0, 0); map.put(1, 11); map.put(2, 22); List<Long> keys = map.keys(); Collections.sort(keys); assertEquals(keys, Lists.newArrayList(0l, 1l, 2l)); List<Long> values = map.values(); Collections.sort(values); assertEquals(values, Lists.newArrayList(0l, 11l, 22l)); map.put(1, 111); keys = map.keys(); Collections.sort(keys); assertEquals(keys, Lists.newArrayList(0l, 1l, 2l)); values = map.values(); Collections.sort(values); assertEquals(values, Lists.newArrayList(0l, 22l, 111l)); map.clear(); assertTrue(map.isEmpty()); } @Test public void testHashConflictWithDeletion() { final int Buckets = 16; ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(Buckets, 1); // Pick 2 keys that fall into the same bucket long key1 = 1; long key2 = 27; int bucket1 = ConcurrentLongLongHashMap.signSafeMod(ConcurrentLongLongHashMap.hash(key1), Buckets); int bucket2 = ConcurrentLongLongHashMap.signSafeMod(ConcurrentLongLongHashMap.hash(key2), Buckets); assertEquals(bucket1, bucket2); final long value1 = 1; final long value2 = 2; final long value1Overwrite = 3; final long value2Overwrite = 3; assertEquals(map.put(key1, value1), -1); assertEquals(map.put(key2, value2), -1); assertEquals(map.size(), 2); assertEquals(map.remove(key1), value1); assertEquals(map.size(), 1); assertEquals(map.put(key1, value1Overwrite), -1); assertEquals(map.size(), 2); assertEquals(map.remove(key1), value1Overwrite); assertEquals(map.size(), 1); assertEquals(map.put(key2, value2Overwrite), value2); assertEquals(map.get(key2), value2Overwrite); assertEquals(map.size(), 1); assertEquals(map.remove(key2), value2Overwrite); assertTrue(map.isEmpty()); } @Test public void testPutIfAbsent() { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(); assertEquals(map.putIfAbsent(1, 11), -1); assertEquals(map.get(1), 11); assertEquals(map.putIfAbsent(1, 111), 11); assertEquals(map.get(1), 11); } @Test public void testComputeIfAbsent() { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(16, 1); AtomicLong counter = new AtomicLong(); LongLongFunction provider = new LongLongFunction() { public long apply(long key) { return counter.getAndIncrement(); } }; assertEquals(map.computeIfAbsent(0, provider), 0); assertEquals(map.get(0), 0); assertEquals(map.computeIfAbsent(1, provider), 1); assertEquals(map.get(1), 1); assertEquals(map.computeIfAbsent(1, provider), 1); assertEquals(map.get(1), 1); assertEquals(map.computeIfAbsent(2, provider), 2); assertEquals(map.get(2), 2); } @Test public void testAddAndGet() { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(16, 1); assertEquals(map.addAndGet(0, 0), 0); assertEquals(map.containsKey(0), true); assertEquals(map.get(0), 0); assertEquals(map.containsKey(5), false); assertEquals(map.addAndGet(0, 5), 5); assertEquals(map.get(0), 5); assertEquals(map.addAndGet(0, 1), 6); assertEquals(map.get(0), 6); assertEquals(map.addAndGet(0, -2), 4); assertEquals(map.get(0), 4); // Cannot bring to value to negative try { map.addAndGet(0, -5); fail("should have failed"); } catch (IllegalArgumentException e) { // ok } assertEquals(map.get(0), 4); } @Test public void testRemoveIf() { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(16, 1); map.put(1L, 1L); map.put(2L, 2L); map.put(3L, 3L); map.put(4L, 4L); map.removeIf(key -> key < 3); assertFalse(map.containsKey(1L)); assertFalse(map.containsKey(2L)); assertTrue(map.containsKey(3L)); assertTrue(map.containsKey(4L)); assertEquals(2, map.size()); } @Test public void testRemoveIfValue() { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(16, 1); map.put(1L, 1L); map.put(2L, 2L); map.put(3L, 1L); map.put(4L, 2L); map.removeIf((key, value) -> value < 2); assertFalse(map.containsKey(1L)); assertTrue(map.containsKey(2L)); assertFalse(map.containsKey(3L)); assertTrue(map.containsKey(4L)); assertEquals(2, map.size()); } @Test public void testIvalidKeys() { ConcurrentLongLongHashMap map = new ConcurrentLongLongHashMap(16, 1); try { map.put(-5, 4); fail("should have failed"); } catch (IllegalArgumentException e) { // ok } try { map.get(-1); fail("should have failed"); } catch (IllegalArgumentException e) { // ok } try { map.containsKey(-1); fail("should have failed"); } catch (IllegalArgumentException e) { // ok } try { map.putIfAbsent(-1, 1); fail("should have failed"); } catch (IllegalArgumentException e) { // ok } try { map.computeIfAbsent(-1, new LongLongFunction() { @Override public long apply(long key) { return 1; } }); fail("should have failed"); } catch (IllegalArgumentException e) { // ok } } @Test public void testAsMap() { ConcurrentLongLongHashMap lmap = new ConcurrentLongLongHashMap(16, 1); lmap.put(1, 11); lmap.put(2, 22); lmap.put(3, 33); Map<Long, Long> map = Maps.newTreeMap(); map.put(1l, 11l); map.put(2l, 22l); map.put(3l, 33l); assertEquals(map, lmap.asMap()); } }