Home | History | Annotate | Download | only in cache
      1 /*
      2  * Copyright (C) 2011 The Guava Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.common.cache;
     18 
     19 import static com.google.common.cache.CacheBuilder.EMPTY_STATS;
     20 import static com.google.common.cache.LocalCacheTest.SMALL_MAX_SIZE;
     21 import static com.google.common.cache.TestingCacheLoaders.identityLoader;
     22 import static org.junit.contrib.truth.Truth.ASSERT;
     23 
     24 import com.google.common.cache.LocalCache.LocalLoadingCache;
     25 import com.google.common.cache.LocalCache.Segment;
     26 import com.google.common.collect.ImmutableMap;
     27 import com.google.common.collect.ImmutableSet;
     28 import com.google.common.collect.Maps;
     29 import com.google.common.testing.NullPointerTester;
     30 import com.google.common.util.concurrent.Callables;
     31 
     32 import junit.framework.TestCase;
     33 
     34 import java.lang.Thread.UncaughtExceptionHandler;
     35 import java.util.Collection;
     36 import java.util.Map;
     37 import java.util.Set;
     38 import java.util.concurrent.Callable;
     39 import java.util.concurrent.ConcurrentMap;
     40 import java.util.concurrent.CountDownLatch;
     41 import java.util.concurrent.TimeUnit;
     42 import java.util.concurrent.atomic.AtomicReference;
     43 
     44 /**
     45  * @author Charles Fry
     46  */
     47 public class LocalLoadingCacheTest extends TestCase {
     48 
     49   private static <K, V> LocalLoadingCache<K, V> makeCache(
     50       CacheBuilder<K, V> builder, CacheLoader<? super K, V> loader) {
     51     return new LocalLoadingCache<K, V>(builder, loader);
     52   }
     53 
     54   private CacheBuilder<Object, Object> createCacheBuilder() {
     55     return new CacheBuilder<Object, Object>();
     56   }
     57 
     58   // constructor tests
     59 
     60   public void testComputingFunction() {
     61     CacheLoader<Object, Object> loader = new CacheLoader<Object, Object>() {
     62       @Override
     63       public Object load(Object from) {
     64         return new Object();
     65       }
     66     };
     67     LocalLoadingCache<Object, Object> cache = makeCache(createCacheBuilder(), loader);
     68     assertSame(loader, cache.localCache.defaultLoader);
     69   }
     70 
     71   // null parameters test
     72 
     73   public void testNullParameters() throws Exception {
     74     NullPointerTester tester = new NullPointerTester();
     75     tester.setDefault(Callable.class, Callables.returning(null));
     76     CacheLoader<Object, Object> loader = identityLoader();
     77     tester.testAllPublicInstanceMethods(makeCache(createCacheBuilder(), loader));
     78   }
     79 
     80   // stats tests
     81 
     82   public void testStats() {
     83     CacheBuilder<Object, Object> builder = createCacheBuilder()
     84         .concurrencyLevel(1)
     85         .maximumSize(2);
     86     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
     87     assertEquals(EMPTY_STATS, cache.stats());
     88 
     89     Object one = new Object();
     90     cache.getUnchecked(one);
     91     CacheStats stats = cache.stats();
     92     assertEquals(1, stats.requestCount());
     93     assertEquals(0, stats.hitCount());
     94     assertEquals(0.0, stats.hitRate());
     95     assertEquals(1, stats.missCount());
     96     assertEquals(1.0, stats.missRate());
     97     assertEquals(1, stats.loadCount());
     98     long totalLoadTime = stats.totalLoadTime();
     99     assertTrue(totalLoadTime > 0);
    100     assertTrue(stats.averageLoadPenalty() > 0.0);
    101     assertEquals(0, stats.evictionCount());
    102 
    103     cache.getUnchecked(one);
    104     stats = cache.stats();
    105     assertEquals(2, stats.requestCount());
    106     assertEquals(1, stats.hitCount());
    107     assertEquals(1.0/2, stats.hitRate());
    108     assertEquals(1, stats.missCount());
    109     assertEquals(1.0/2, stats.missRate());
    110     assertEquals(1, stats.loadCount());
    111     assertEquals(0, stats.evictionCount());
    112 
    113     Object two = new Object();
    114     cache.getUnchecked(two);
    115     stats = cache.stats();
    116     assertEquals(3, stats.requestCount());
    117     assertEquals(1, stats.hitCount());
    118     assertEquals(1.0/3, stats.hitRate());
    119     assertEquals(2, stats.missCount());
    120     assertEquals(2.0/3, stats.missRate());
    121     assertEquals(2, stats.loadCount());
    122     assertTrue(stats.totalLoadTime() > totalLoadTime);
    123     totalLoadTime = stats.totalLoadTime();
    124     assertTrue(stats.averageLoadPenalty() > 0.0);
    125     assertEquals(0, stats.evictionCount());
    126 
    127     Object three = new Object();
    128     cache.getUnchecked(three);
    129     stats = cache.stats();
    130     assertEquals(4, stats.requestCount());
    131     assertEquals(1, stats.hitCount());
    132     assertEquals(1.0/4, stats.hitRate());
    133     assertEquals(3, stats.missCount());
    134     assertEquals(3.0/4, stats.missRate());
    135     assertEquals(3, stats.loadCount());
    136     assertTrue(stats.totalLoadTime() > totalLoadTime);
    137     totalLoadTime = stats.totalLoadTime();
    138     assertTrue(stats.averageLoadPenalty() > 0.0);
    139     assertEquals(1, stats.evictionCount());
    140   }
    141 
    142   public void testStatsNoops() {
    143     CacheBuilder<Object, Object> builder = createCacheBuilder()
    144         .concurrencyLevel(1);
    145     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
    146     ConcurrentMap<Object, Object> map = cache.localCache; // modifiable map view
    147     assertEquals(EMPTY_STATS, cache.stats());
    148 
    149     Object one = new Object();
    150     assertNull(map.put(one, one));
    151     assertSame(one, map.get(one));
    152     assertTrue(map.containsKey(one));
    153     assertTrue(map.containsValue(one));
    154     Object two = new Object();
    155     assertSame(one, map.replace(one, two));
    156     assertTrue(map.containsKey(one));
    157     assertFalse(map.containsValue(one));
    158     Object three = new Object();
    159     assertTrue(map.replace(one, two, three));
    160     assertTrue(map.remove(one, three));
    161     assertFalse(map.containsKey(one));
    162     assertFalse(map.containsValue(one));
    163     assertNull(map.putIfAbsent(two, three));
    164     assertSame(three, map.remove(two));
    165     assertNull(map.put(three, one));
    166     assertNull(map.put(one, two));
    167 
    168     Set<Map.Entry<Object, Object>> entries = map.entrySet();
    169     ASSERT.that(entries).hasContentsAnyOrder(
    170         Maps.immutableEntry(three, one), Maps.immutableEntry(one, two));
    171     Set<Object> keys = map.keySet();
    172     ASSERT.that(keys).hasContentsAnyOrder(one, three);
    173     Collection<Object> values = map.values();
    174     ASSERT.that(values).hasContentsAnyOrder(one, two);
    175 
    176     map.clear();
    177 
    178     assertEquals(EMPTY_STATS, cache.stats());
    179   }
    180 
    181   public void testDisableStats() {
    182     CacheBuilder<Object, Object> builder = createCacheBuilder()
    183         .concurrencyLevel(1)
    184         .maximumSize(2)
    185         .disableStats();
    186     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
    187     assertEquals(EMPTY_STATS, cache.stats());
    188 
    189     Object one = new Object();
    190     cache.getUnchecked(one);
    191     assertEquals(EMPTY_STATS, cache.stats());
    192 
    193     cache.getUnchecked(one);
    194     assertEquals(EMPTY_STATS, cache.stats());
    195 
    196     Object two = new Object();
    197     cache.getUnchecked(two);
    198     assertEquals(EMPTY_STATS, cache.stats());
    199 
    200     Object three = new Object();
    201     cache.getUnchecked(three);
    202     assertEquals(EMPTY_STATS, cache.stats());
    203   }
    204 
    205   // asMap tests
    206 
    207   public void testAsMap() {
    208     CacheBuilder<Object, Object> builder = createCacheBuilder();
    209     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
    210     assertEquals(EMPTY_STATS, cache.stats());
    211 
    212     Object one = new Object();
    213     Object two = new Object();
    214     Object three = new Object();
    215 
    216     ConcurrentMap<Object, Object> map = cache.asMap();
    217     assertNull(map.put(one, two));
    218     assertSame(two, map.get(one));
    219     map.putAll(ImmutableMap.of(two, three));
    220     assertSame(three, map.get(two));
    221     assertSame(two, map.putIfAbsent(one, three));
    222     assertSame(two, map.get(one));
    223     assertNull(map.putIfAbsent(three, one));
    224     assertSame(one, map.get(three));
    225     assertSame(two, map.replace(one, three));
    226     assertSame(three, map.get(one));
    227     assertFalse(map.replace(one, two, three));
    228     assertSame(three, map.get(one));
    229     assertTrue(map.replace(one, three, two));
    230     assertSame(two, map.get(one));
    231     assertEquals(3, map.size());
    232 
    233     map.clear();
    234     assertTrue(map.isEmpty());
    235     assertEquals(0, map.size());
    236 
    237     cache.getUnchecked(one);
    238     assertEquals(1, map.size());
    239     assertSame(one, map.get(one));
    240     assertTrue(map.containsKey(one));
    241     assertTrue(map.containsValue(one));
    242     assertSame(one, map.remove(one));
    243     assertEquals(0, map.size());
    244 
    245     cache.getUnchecked(one);
    246     assertEquals(1, map.size());
    247     assertFalse(map.remove(one, two));
    248     assertTrue(map.remove(one, one));
    249     assertEquals(0, map.size());
    250 
    251     cache.getUnchecked(one);
    252     Map<Object, Object> newMap = ImmutableMap.of(one, one);
    253     assertEquals(newMap, map);
    254     assertEquals(newMap.entrySet(), map.entrySet());
    255     assertEquals(newMap.keySet(), map.keySet());
    256     Set<Object> expectedValues = ImmutableSet.of(one);
    257     Set<Object> actualValues = ImmutableSet.copyOf(map.values());
    258     assertEquals(expectedValues, actualValues);
    259   }
    260 
    261   /**
    262    * Lookups on the map view shouldn't impact the recency queue.
    263    */
    264   public void testAsMapRecency() {
    265     CacheBuilder<Object, Object> builder = createCacheBuilder()
    266         .concurrencyLevel(1)
    267         .maximumSize(SMALL_MAX_SIZE);
    268     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
    269     Segment<Object, Object> segment = cache.localCache.segments[0];
    270     ConcurrentMap<Object, Object> map = cache.asMap();
    271 
    272     Object one = new Object();
    273     assertSame(one, cache.getUnchecked(one));
    274     assertTrue(segment.recencyQueue.isEmpty());
    275     assertSame(one, map.get(one));
    276     assertSame(one, segment.recencyQueue.peek().getKey());
    277     assertSame(one, cache.getUnchecked(one));
    278     assertFalse(segment.recencyQueue.isEmpty());
    279   }
    280 
    281   public void testRecursiveComputation() throws InterruptedException {
    282     final AtomicReference<LoadingCache<Integer, String>> cacheRef =
    283         new AtomicReference<LoadingCache<Integer, String>>();
    284     CacheLoader<Integer, String> recursiveLoader = new CacheLoader<Integer, String>() {
    285       @Override
    286       public String load(Integer key) {
    287         if (key > 0) {
    288           return key + ", " + cacheRef.get().getUnchecked(key - 1);
    289         } else {
    290           return "0";
    291         }
    292       }
    293     };
    294 
    295     LoadingCache<Integer, String> recursiveCache = new CacheBuilder<Integer, String>()
    296         .weakKeys()
    297         .weakValues()
    298         .build(recursiveLoader);
    299     cacheRef.set(recursiveCache);
    300     assertEquals("3, 2, 1, 0", recursiveCache.getUnchecked(3));
    301 
    302     recursiveLoader = new CacheLoader<Integer, String>() {
    303       @Override
    304       public String load(Integer key) {
    305         return cacheRef.get().getUnchecked(key);
    306       }
    307     };
    308 
    309     recursiveCache = new CacheBuilder<Integer, String>()
    310         .weakKeys()
    311         .weakValues()
    312         .build(recursiveLoader);
    313     cacheRef.set(recursiveCache);
    314 
    315     // tells the test when the compution has completed
    316     final CountDownLatch doneSignal = new CountDownLatch(1);
    317 
    318     Thread thread = new Thread() {
    319       @Override
    320       public void run() {
    321         try {
    322           cacheRef.get().getUnchecked(3);
    323         } finally {
    324           doneSignal.countDown();
    325         }
    326       }
    327     };
    328     thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
    329       @Override
    330       public void uncaughtException(Thread t, Throwable e) {}
    331     });
    332     thread.start();
    333 
    334     boolean done = doneSignal.await(1, TimeUnit.SECONDS);
    335     if (!done) {
    336       StringBuilder builder = new StringBuilder();
    337       for (StackTraceElement trace : thread.getStackTrace()) {
    338         builder.append("\tat ").append(trace).append('\n');
    339       }
    340       fail(builder.toString());
    341     }
    342   }
    343 }
    344