/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2006-2016, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.util; import java.util.HashMap; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import org.junit.*; import static org.junit.Assert.*; /** * Tests {@link SoftValueHashMap}. * * * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux * @author Ugo Moschini */ public final class SoftValueHashMapTest { /** * The size of the test sets to be created. */ private static final int SAMPLE_SIZE = 200; private static int NUMTHREADS = 16; private static int THREAD_CYCLES= (int)1E1; private static int TEST_CYCLES = (int) 1E1; /** * Tests the {@link SoftValueHashMap} using strong references. The tested * {@link SoftValueHashMap} should behave like a standard {@link Map} object. */ @Test public void testStrongReferences() { final Random random = getRandom(); for (int pass=0; pass<4; pass++) { // make sure we can keep as many strong references as the sample, since there is no guarantee // the distribution of random.nextBoolean() is uniform in the short term final SoftValueHashMap<Integer,Integer> softMap = new SoftValueHashMap<Integer,Integer>(SAMPLE_SIZE); final HashMap<Integer,Integer> strongMap = new HashMap<Integer,Integer>(); for (int i=0; i<SAMPLE_SIZE; i++) { Integer key = random.nextInt(SAMPLE_SIZE); final Integer value = random.nextInt(SAMPLE_SIZE); if (random.nextBoolean()) // test from time to time with the null key key = null; assertEquals("containsKey:", strongMap.containsKey(key), softMap.containsKey(key)); assertEquals("containsValue:", strongMap.containsValue(value), softMap.containsValue(value)); assertSame ("get:", strongMap.get(key), softMap.get(key)); assertEquals("equals:", strongMap, softMap); if (random.nextBoolean()) { // Test addition. assertSame("put:", strongMap.put(key, value), softMap.put(key, value)); } else { // Test remove assertSame("remove:", strongMap.remove(key), softMap.remove(key)); } assertEquals("equals:", strongMap, softMap); } } } /** * Tests the {@link SoftValueHashMap} using soft references. * In this test, we have to keep in mind than some elements * in {@code softMap} may disappear at any time. */ @Test public void testSoftReferences() throws InterruptedException { final Random random = getRandom(); final SoftValueHashMap<Integer,Integer> softMap = new SoftValueHashMap<Integer,Integer>(); final HashMap<Integer,Integer> strongMap = new HashMap<Integer,Integer>(); for (int pass=0; pass<2; pass++) { int count = 0; softMap.clear(); strongMap.clear(); for (int i=0; i<SAMPLE_SIZE; i++) { // We really want new instances below. Integer key = new Integer(random.nextInt(SAMPLE_SIZE)); final Integer value = new Integer(random.nextInt(SAMPLE_SIZE)); if (random.nextBoolean()) // test from time to time with the null key key = null; if (random.nextBoolean()) { /* * Test addition. */ final Integer softPrevious = softMap .put(key, value); final Integer strongPrevious = strongMap.put(key, value); if (softPrevious == null) { // If the element was not in the SoftValueHashMap (i.e. if the garbage // collector has cleared it), then it must not been in HashMap neither // (otherwise GC should not have cleared it). assertNull("put:", strongPrevious); count++; // Count only the new values. } else { assertNotSame(value, softPrevious); } if (strongPrevious != null) { // Note: If 'strongPrevious==null', 'softPrevious' may not // be null if GC has not collected its entry yet. assertSame("put:", strongPrevious, softPrevious); } } else { /* * Test remove */ final Integer softPrevious = softMap.get(key); final Integer strongPrevious = strongMap.remove(key); if (strongPrevious != null) { assertSame("remove:", strongPrevious, softPrevious); } } assertTrue("containsAll:", softMap.entrySet().containsAll(strongMap.entrySet())); } // Do our best to lets GC finish its work. for (int i=0; i<20; i++) { Runtime.getRuntime().gc(); Runtime.getRuntime().runFinalization(); } assertTrue(softMap.isValid()); assertTrue("size:", softMap.size() <= count); /* * Make sure that all values are of the correct type. More specifically, we * want to make sure that we didn't forget to convert some Reference object. */ for(Object value : softMap.values()) { assertTrue(value instanceof Integer); assertNotNull(value); } } } /** * Tests the {@link SoftValueHashMap} with threads performing a sequence of put * and get operations on the cache. * @throws InterruptedException */ @Test public void testGetPutInMultithreadEnv() throws InterruptedException { final Random random = getRandom(); SoftValueHashMap<Integer, Integer> cache = new SoftValueHashMap<Integer, Integer>(); // create threads ExecutorService executor = Executors.newFixedThreadPool(NUMTHREADS); for (int iter = 0; iter < TEST_CYCLES; iter++) { final CountDownLatch latch = new CountDownLatch(NUMTHREADS); for(int i=0; i < NUMTHREADS; i++) { Runnable th = new CacheTestThreadGetPut(cache, random,latch); executor.execute(th); } latch.await(); } executor.shutdown(); executor.awaitTermination(3, TimeUnit.SECONDS); } /** * Tests the {@link SoftValueHashMap} with threads that perform a sequence of put and get * and threads that access elements in cache through the iterator * @throws InterruptedException */ @Test public void testGetPutIteratorsInMultithreadEnv() throws InterruptedException { final Random random = getRandom(); SoftValueHashMap<Integer, Integer> cache = new SoftValueHashMap<Integer, Integer>(); // create threads ExecutorService executor = Executors.newFixedThreadPool(NUMTHREADS); for (int iter = 0; iter < TEST_CYCLES; iter++) { final CountDownLatch latch = new CountDownLatch(NUMTHREADS); // Threads that fill the cache with random values for(int i=0; i < NUMTHREADS/2; i++) { Runnable th = new CacheTestThreadGetPut(cache, random,latch); executor.execute(th); } // Threads that use the iterator on the cache for(int i=0; i < NUMTHREADS/2; i++) { Runnable th = new CacheTestThreadIterators(cache, latch); executor.execute(th); } latch.await(); } executor.shutdown(); executor.awaitTermination(3, TimeUnit.SECONDS); } private class CacheTestThreadGetPut implements Runnable{ private SoftValueHashMap<Integer, Integer> cache; private Random random; private CountDownLatch latch; public CacheTestThreadGetPut(SoftValueHashMap<Integer, Integer> cache, Random random, CountDownLatch latch) { this.cache = cache; this.random = random; this.latch=latch; } public void run() { for(int i=0; i<THREAD_CYCLES; i++) { final Integer key = new Integer(random.nextInt(SAMPLE_SIZE)); final Integer value = new Integer(random.nextInt(SAMPLE_SIZE)); if (random.nextBoolean()) { Object o = cache.put(key, value); if(o!= null) assertTrue("Put in multithread", o instanceof Integer); } else { Object o = cache.get(key); if(o!= null) assertTrue("Get in multithread", o instanceof Integer); } } latch.countDown(); } } private class CacheTestThreadIterators implements Runnable{ private SoftValueHashMap<Integer, Integer> cache; private CountDownLatch latch; public CacheTestThreadIterators(SoftValueHashMap<Integer, Integer> cache, CountDownLatch latch) { this.cache = cache; this.latch=latch; } public void run() { for(int i=0; i<THREAD_CYCLES; i++) { for(Object value : cache.values()) { if(value != null){ assertTrue("Value from iterator:", value instanceof Integer); } } } latch.countDown(); } } private Random getRandom() { long seed = System.currentTimeMillis() + hashCode(); Random random = new Random(seed); Logger.getLogger(this.getClass().getName()).info("Using Random Seed: "+seed); return random; } }