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"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 
     15 package com.google.common.cache;
     16 
     17 import static com.google.common.cache.LocalCache.Strength.STRONG;
     18 import static com.google.common.collect.Maps.immutableEntry;
     19 import static com.google.common.truth.Truth.assertThat;
     20 
     21 import com.google.common.base.Function;
     22 import com.google.common.cache.LocalCache.Strength;
     23 import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener;
     24 import com.google.common.collect.ImmutableSet;
     25 import com.google.common.collect.Iterables;
     26 
     27 import junit.framework.TestCase;
     28 
     29 import java.lang.ref.WeakReference;
     30 
     31 /**
     32  * Tests of basic {@link LoadingCache} operations with all possible combinations of key & value
     33  * strengths.
     34  *
     35  * @author mike nonemacher
     36  */
     37 public class CacheReferencesTest extends TestCase {
     38 
     39   private static final CacheLoader<Key,String> KEY_TO_STRING_LOADER =
     40       new CacheLoader<Key, String>() {
     41         @Override public String load(Key key) {
     42           return key.toString();
     43         }
     44       };
     45 
     46   private CacheBuilderFactory factoryWithAllKeyStrengths() {
     47     return new CacheBuilderFactory()
     48         .withKeyStrengths(ImmutableSet.of(STRONG, Strength.WEAK))
     49         .withValueStrengths(ImmutableSet.of(STRONG, Strength.WEAK, Strength.SOFT));
     50   }
     51 
     52   private Iterable<LoadingCache<Key, String>> caches() {
     53     CacheBuilderFactory factory = factoryWithAllKeyStrengths();
     54     return Iterables.transform(factory.buildAllPermutations(),
     55         new Function<CacheBuilder<Object, Object>, LoadingCache<Key, String>>() {
     56           @Override public LoadingCache<Key, String> apply(CacheBuilder<Object, Object> builder) {
     57             return builder.build(KEY_TO_STRING_LOADER);
     58           }
     59         });
     60   }
     61 
     62   public void testContainsKeyAndValue() {
     63     for (LoadingCache<Key, String> cache : caches()) {
     64       // maintain strong refs so these won't be collected, regardless of cache's key/value strength
     65       Key key = new Key(1);
     66       String value = key.toString();
     67       assertSame(value, cache.getUnchecked(key));
     68       assertTrue(cache.asMap().containsKey(key));
     69       assertTrue(cache.asMap().containsValue(value));
     70       assertEquals(1, cache.size());
     71     }
     72   }
     73 
     74   public void testClear() {
     75     for (LoadingCache<Key, String> cache : caches()) {
     76       Key key = new Key(1);
     77       String value = key.toString();
     78       assertSame(value, cache.getUnchecked(key));
     79       assertFalse(cache.asMap().isEmpty());
     80       cache.invalidateAll();
     81       assertEquals(0, cache.size());
     82       assertTrue(cache.asMap().isEmpty());
     83       assertFalse(cache.asMap().containsKey(key));
     84       assertFalse(cache.asMap().containsValue(value));
     85     }
     86   }
     87 
     88   public void testKeySetEntrySetValues() {
     89     for (LoadingCache<Key, String> cache : caches()) {
     90       Key key1 = new Key(1);
     91       String value1 = key1.toString();
     92       Key key2 = new Key(2);
     93       String value2 = key2.toString();
     94       assertSame(value1, cache.getUnchecked(key1));
     95       assertSame(value2, cache.getUnchecked(key2));
     96       assertEquals(ImmutableSet.of(key1, key2), cache.asMap().keySet());
     97       assertThat(cache.asMap().values()).has().exactly(value1, value2);
     98       assertEquals(ImmutableSet.of(immutableEntry(key1, value1), immutableEntry(key2, value2)),
     99           cache.asMap().entrySet());
    100     }
    101   }
    102 
    103   public void testInvalidate() {
    104     for (LoadingCache<Key, String> cache : caches()) {
    105       Key key1 = new Key(1);
    106       String value1 = key1.toString();
    107       Key key2 = new Key(2);
    108       String value2 = key2.toString();
    109       assertSame(value1, cache.getUnchecked(key1));
    110       assertSame(value2, cache.getUnchecked(key2));
    111       cache.invalidate(key1);
    112       assertFalse(cache.asMap().containsKey(key1));
    113       assertTrue(cache.asMap().containsKey(key2));
    114       assertEquals(1, cache.size());
    115       assertEquals(ImmutableSet.of(key2), cache.asMap().keySet());
    116       assertThat(cache.asMap().values()).has().item(value2);
    117       assertEquals(ImmutableSet.of(immutableEntry(key2, value2)), cache.asMap().entrySet());
    118     }
    119   }
    120 
    121   // fails in Maven with 64-bit JDK: http://code.google.com/p/guava-libraries/issues/detail?id=1568
    122 
    123   private void assertCleanup(LoadingCache<Integer, String> cache,
    124       CountingRemovalListener<Integer, String> removalListener) {
    125 
    126     // initialSize will most likely be 2, but it's possible for the GC to have already run, so we'll
    127     // observe a size of 1
    128     long initialSize = cache.size();
    129     assertTrue(initialSize == 1 || initialSize == 2);
    130 
    131     // wait up to 5s
    132     byte[] filler = new byte[1024];
    133     for (int i = 0; i < 500; i++) {
    134       System.gc();
    135 
    136       CacheTesting.drainReferenceQueues(cache);
    137       if (cache.size() == 1) {
    138         break;
    139       }
    140       try {
    141         Thread.sleep(10);
    142       } catch (InterruptedException e) { /* ignore */}
    143       try {
    144         // Fill up heap so soft references get cleared.
    145         filler = new byte[Math.max(filler.length, filler.length * 2)];
    146       } catch (OutOfMemoryError e) {}
    147     }
    148 
    149     CacheTesting.processPendingNotifications(cache);
    150     assertEquals(1, cache.size());
    151     assertEquals(1, removalListener.getCount());
    152   }
    153 
    154   // A simple type whose .toString() will return the same value each time, but without maintaining
    155   // a strong reference to that value.
    156   static class Key {
    157     private final int value;
    158     private WeakReference<String> toString;
    159 
    160     Key(int value) {
    161       this.value = value;
    162     }
    163 
    164     @Override public synchronized String toString() {
    165       String s;
    166       if (toString != null) {
    167         s = toString.get();
    168         if (s != null) {
    169           return s;
    170         }
    171       }
    172       s = Integer.toString(value);
    173       toString = new WeakReference<String>(s);
    174       return s;
    175     }
    176   }
    177 }
    178