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.CacheTesting.checkEmpty;
     18 import static com.google.common.cache.CacheTesting.checkValidState;
     19 import static com.google.common.cache.TestingCacheLoaders.identityLoader;
     20 import static com.google.common.truth.Truth.assertThat;
     21 import static java.util.concurrent.TimeUnit.DAYS;
     22 import static java.util.concurrent.TimeUnit.SECONDS;
     23 
     24 import com.google.common.base.Function;
     25 import com.google.common.cache.CacheBuilderFactory.DurationSpec;
     26 import com.google.common.cache.LocalCache.Strength;
     27 import com.google.common.collect.ImmutableMap;
     28 import com.google.common.collect.ImmutableSet;
     29 import com.google.common.collect.Iterables;
     30 import com.google.common.collect.Iterators;
     31 import com.google.common.collect.Lists;
     32 import com.google.common.collect.Maps;
     33 import com.google.common.testing.EqualsTester;
     34 
     35 import junit.framework.TestCase;
     36 
     37 import java.util.Collection;
     38 import java.util.List;
     39 import java.util.Map;
     40 import java.util.Map.Entry;
     41 import java.util.Set;
     42 
     43 /**
     44  * {@link LoadingCache} tests that deal with caches that actually contain some key-value mappings.
     45  *
     46  * @author mike nonemacher
     47  */
     48 
     49 public class PopulatedCachesTest extends TestCase {
     50   // we use integers as keys; make sure the range covers some values that ARE cached by
     51   // Integer.valueOf(int), and some that are not cached. (127 is the highest cached value.)
     52   static final int WARMUP_MIN = 120;
     53   static final int WARMUP_MAX = 135;
     54   static final int WARMUP_SIZE = WARMUP_MAX - WARMUP_MIN;
     55 
     56   public void testSize_populated() {
     57     for (LoadingCache<Object, Object> cache : caches()) {
     58       // don't let the entries get GCed
     59       List<Entry<Object, Object>> warmed = warmUp(cache);
     60       assertEquals(WARMUP_SIZE, cache.size());
     61       assertMapSize(cache.asMap(), WARMUP_SIZE);
     62       checkValidState(cache);
     63     }
     64   }
     65 
     66   public void testContainsKey_found() {
     67     for (LoadingCache<Object, Object> cache : caches()) {
     68       // don't let the entries get GCed
     69       List<Entry<Object, Object>> warmed = warmUp(cache);
     70       for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) {
     71         Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN);
     72         assertTrue(cache.asMap().containsKey(entry.getKey()));
     73         assertTrue(cache.asMap().containsValue(entry.getValue()));
     74         // this getUnchecked() call shouldn't be a cache miss; verified below
     75         assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey()));
     76       }
     77       assertEquals(WARMUP_SIZE, cache.stats().missCount());
     78       checkValidState(cache);
     79     }
     80   }
     81 
     82   public void testPut_populated() {
     83     for (LoadingCache<Object, Object> cache : caches()) {
     84       // don't let the entries get GCed
     85       List<Entry<Object, Object>> warmed = warmUp(cache);
     86       for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) {
     87         Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN);
     88         Object newValue = new Object();
     89         assertSame(entry.getValue(), cache.asMap().put(entry.getKey(), newValue));
     90         // don't let the new entry get GCed
     91         warmed.add(entryOf(entry.getKey(), newValue));
     92         Object newKey = new Object();
     93         assertNull(cache.asMap().put(newKey, entry.getValue()));
     94         // this getUnchecked() call shouldn't be a cache miss; verified below
     95         assertEquals(newValue, cache.getUnchecked(entry.getKey()));
     96         assertEquals(entry.getValue(), cache.getUnchecked(newKey));
     97         // don't let the new entry get GCed
     98         warmed.add(entryOf(newKey, entry.getValue()));
     99       }
    100       assertEquals(WARMUP_SIZE, cache.stats().missCount());
    101       checkValidState(cache);
    102     }
    103   }
    104 
    105   public void testPutIfAbsent_populated() {
    106     for (LoadingCache<Object, Object> cache : caches()) {
    107       // don't let the entries get GCed
    108       List<Entry<Object, Object>> warmed = warmUp(cache);
    109       for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) {
    110         Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN);
    111         Object newValue = new Object();
    112         assertSame(entry.getValue(), cache.asMap().putIfAbsent(entry.getKey(), newValue));
    113         Object newKey = new Object();
    114         assertNull(cache.asMap().putIfAbsent(newKey, entry.getValue()));
    115         // this getUnchecked() call shouldn't be a cache miss; verified below
    116         assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey()));
    117         assertEquals(entry.getValue(), cache.getUnchecked(newKey));
    118         // don't let the new entry get GCed
    119         warmed.add(entryOf(newKey, entry.getValue()));
    120       }
    121       assertEquals(WARMUP_SIZE, cache.stats().missCount());
    122       checkValidState(cache);
    123     }
    124   }
    125 
    126   public void testPutAll_populated() {
    127     for (LoadingCache<Object, Object> cache : caches()) {
    128       // don't let the entries get GCed
    129       List<Entry<Object, Object>> warmed = warmUp(cache);
    130       Object newKey = new Object();
    131       Object newValue = new Object();
    132       cache.asMap().putAll(ImmutableMap.of(newKey, newValue));
    133       // this getUnchecked() call shouldn't be a cache miss; verified below
    134       assertEquals(newValue, cache.getUnchecked(newKey));
    135       assertEquals(WARMUP_SIZE, cache.stats().missCount());
    136       checkValidState(cache);
    137     }
    138   }
    139 
    140   public void testReplace_populated() {
    141     for (LoadingCache<Object, Object> cache : caches()) {
    142       // don't let the entries get GCed
    143       List<Entry<Object, Object>> warmed = warmUp(cache);
    144       for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) {
    145         Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN);
    146         Object newValue = new Object();
    147         assertSame(entry.getValue(), cache.asMap().replace(entry.getKey(), newValue));
    148         assertTrue(cache.asMap().replace(entry.getKey(), newValue, entry.getValue()));
    149         Object newKey = new Object();
    150         assertNull(cache.asMap().replace(newKey, entry.getValue()));
    151         assertFalse(cache.asMap().replace(newKey, entry.getValue(), newValue));
    152         // this getUnchecked() call shouldn't be a cache miss; verified below
    153         assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey()));
    154         assertFalse(cache.asMap().containsKey(newKey));
    155       }
    156       assertEquals(WARMUP_SIZE, cache.stats().missCount());
    157       checkValidState(cache);
    158     }
    159   }
    160 
    161   public void testRemove_byKey() {
    162     for (LoadingCache<Object, Object> cache : caches()) {
    163       // don't let the entries get GCed
    164       List<Entry<Object, Object>> warmed = warmUp(cache);
    165       for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) {
    166         Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN);
    167         Object key = entry.getKey();
    168         assertEquals(entry.getValue(), cache.asMap().remove(key));
    169         assertNull(cache.asMap().remove(key));
    170         assertFalse(cache.asMap().containsKey(key));
    171       }
    172       checkEmpty(cache);
    173     }
    174   }
    175 
    176   public void testRemove_byKeyAndValue() {
    177     for (LoadingCache<Object, Object> cache : caches()) {
    178       // don't let the entries get GCed
    179       List<Entry<Object, Object>> warmed = warmUp(cache);
    180       for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) {
    181         Object key = warmed.get(i - WARMUP_MIN).getKey();
    182         Object value = warmed.get(i - WARMUP_MIN).getValue();
    183         assertFalse(cache.asMap().remove(key, -1));
    184         assertTrue(cache.asMap().remove(key, value));
    185         assertFalse(cache.asMap().remove(key, -1));
    186         assertFalse(cache.asMap().containsKey(key));
    187       }
    188       checkEmpty(cache);
    189     }
    190   }
    191 
    192   public void testKeySet_populated() {
    193     for (LoadingCache<Object, Object> cache : caches()) {
    194       Set<Object> keys = cache.asMap().keySet();
    195       List<Entry<Object, Object>> warmed = warmUp(cache);
    196 
    197       Set<Object> expected = Maps.newHashMap(cache.asMap()).keySet();
    198       assertThat(keys).has().exactlyAs(expected);
    199       assertThat(keys.toArray()).asList().has().exactlyAs(expected);
    200       assertThat(keys.toArray(new Object[0])).asList().has().exactlyAs(expected);
    201 
    202       new EqualsTester()
    203           .addEqualityGroup(cache.asMap().keySet(), keys)
    204           .addEqualityGroup(ImmutableSet.of())
    205           .testEquals();
    206       assertEquals(WARMUP_SIZE, keys.size());
    207       for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) {
    208         Object key = warmed.get(i - WARMUP_MIN).getKey();
    209         assertTrue(keys.contains(key));
    210         assertTrue(keys.remove(key));
    211         assertFalse(keys.remove(key));
    212         assertFalse(keys.contains(key));
    213       }
    214       checkEmpty(keys);
    215       checkEmpty(cache);
    216     }
    217   }
    218 
    219   public void testValues_populated() {
    220     for (LoadingCache<Object, Object> cache : caches()) {
    221       Collection<Object> values = cache.asMap().values();
    222       List<Entry<Object, Object>> warmed = warmUp(cache);
    223 
    224       Collection<Object> expected = Maps.newHashMap(cache.asMap()).values();
    225       assertThat(values).has().exactlyAs(expected);
    226       assertThat(values.toArray()).asList().has().exactlyAs(expected);
    227       assertThat(values.toArray(new Object[0])).asList().has().exactlyAs(expected);
    228 
    229       assertEquals(WARMUP_SIZE, values.size());
    230       for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) {
    231         Object value = warmed.get(i - WARMUP_MIN).getValue();
    232         assertTrue(values.contains(value));
    233         assertTrue(values.remove(value));
    234         assertFalse(values.remove(value));
    235         assertFalse(values.contains(value));
    236       }
    237       checkEmpty(values);
    238       checkEmpty(cache);
    239     }
    240   }
    241 
    242   @SuppressWarnings("unchecked") // generic array creation
    243 
    244   public void testEntrySet_populated() {
    245     for (LoadingCache<Object, Object> cache : caches()) {
    246       Set<Entry<Object, Object>> entries = cache.asMap().entrySet();
    247       List<Entry<Object, Object>> warmed = warmUp(cache, WARMUP_MIN, WARMUP_MAX);
    248 
    249       Set<?> expected = Maps.newHashMap(cache.asMap()).entrySet();
    250       assertThat(entries).has().exactlyAs((Collection<Entry<Object, Object>>) expected);
    251       assertThat(entries.toArray()).asList().has().exactlyAs((Collection<Object>) expected);
    252       assertThat(entries.toArray(new Entry[0])).asList()
    253           .has().exactlyAs((Collection<Entry>) expected);
    254 
    255       new EqualsTester()
    256           .addEqualityGroup(cache.asMap().entrySet(), entries)
    257           .addEqualityGroup(ImmutableSet.of())
    258           .testEquals();
    259       assertEquals(WARMUP_SIZE, entries.size());
    260       for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) {
    261         Entry<Object, Object> newEntry = warmed.get(i - WARMUP_MIN);
    262         assertTrue(entries.contains(newEntry));
    263         assertTrue(entries.remove(newEntry));
    264         assertFalse(entries.remove(newEntry));
    265         assertFalse(entries.contains(newEntry));
    266       }
    267       checkEmpty(entries);
    268       checkEmpty(cache);
    269     }
    270   }
    271 
    272   public void testWriteThroughEntry() {
    273     for (LoadingCache<Object, Object> cache : caches()) {
    274       cache.getUnchecked(1);
    275       Entry<Object, Object> entry = Iterables.getOnlyElement(cache.asMap().entrySet());
    276       try {
    277         entry.setValue(3);
    278         fail("expected entry.setValue to throw UnsupportedOperationException");
    279       } catch (UnsupportedOperationException expected) {
    280       }
    281       try {
    282         entry.setValue(null);
    283         fail("expected entry.setValue(null) to throw UnsupportedOperationException");
    284       } catch (UnsupportedOperationException expected) {
    285       }
    286       checkValidState(cache);
    287     }
    288   }
    289 
    290   /* ---------------- Local utilities -------------- */
    291 
    292   /**
    293    * Most of the tests in this class run against every one of these caches.
    294    */
    295   private Iterable<LoadingCache<Object, Object>> caches() {
    296     // lots of different ways to configure a LoadingCache
    297     CacheBuilderFactory factory = cacheFactory();
    298     return Iterables.transform(factory.buildAllPermutations(),
    299         new Function<CacheBuilder<Object, Object>, LoadingCache<Object, Object>>() {
    300           @Override public LoadingCache<Object, Object> apply(
    301               CacheBuilder<Object, Object> builder) {
    302             return builder.recordStats().build(identityLoader());
    303           }
    304         });
    305   }
    306 
    307   private CacheBuilderFactory cacheFactory() {
    308     // This is trickier than expected. We plan to put 15 values in each of these (WARMUP_MIN to
    309     // WARMUP_MAX), but the tests assume no values get evicted. Even with a maximumSize of 100, one
    310     // of the values gets evicted. With weak keys, we use identity equality, which means using
    311     // System.identityHashCode, which means the assignment of keys to segments is nondeterministic,
    312     // so more than (maximumSize / #segments) keys could get assigned to the same segment, which
    313     // would cause one to be evicted.
    314     return new CacheBuilderFactory()
    315         .withKeyStrengths(ImmutableSet.of(Strength.STRONG, Strength.WEAK))
    316         .withValueStrengths(ImmutableSet.copyOf(Strength.values()))
    317         .withConcurrencyLevels(ImmutableSet.of(1, 4, 16, 64))
    318         .withMaximumSizes(ImmutableSet.of(400, 1000))
    319         .withInitialCapacities(ImmutableSet.of(0, 1, 10, 100, 1000))
    320         .withExpireAfterWrites(ImmutableSet.of(
    321             // DurationSpec.of(500, MILLISECONDS),
    322             DurationSpec.of(1, SECONDS),
    323             DurationSpec.of(1, DAYS)))
    324         .withExpireAfterAccesses(ImmutableSet.of(
    325             // DurationSpec.of(500, MILLISECONDS),
    326             DurationSpec.of(1, SECONDS),
    327             DurationSpec.of(1, DAYS)))
    328         .withRefreshes(ImmutableSet.of(
    329             // DurationSpec.of(500, MILLISECONDS),
    330             DurationSpec.of(1, SECONDS),
    331             DurationSpec.of(1, DAYS)));
    332   }
    333 
    334   private List<Map.Entry<Object, Object>> warmUp(LoadingCache<Object, Object> cache) {
    335     return warmUp(cache, WARMUP_MIN, WARMUP_MAX);
    336   }
    337 
    338   /**
    339    * Returns the entries that were added to the map, so they won't fall out of a map with weak or
    340    * soft references until the caller drops the reference to the returned entries.
    341    */
    342   private List<Map.Entry<Object, Object>> warmUp(
    343       LoadingCache<Object, Object> cache, int minimum, int maximum) {
    344 
    345     List<Map.Entry<Object, Object>> entries = Lists.newArrayList();
    346     for (int i = minimum; i < maximum; i++) {
    347       Object key = i;
    348       Object value = cache.getUnchecked(key);
    349       entries.add(entryOf(key, value));
    350     }
    351     return entries;
    352   }
    353 
    354   private Entry<Object, Object> entryOf(Object key, Object value) {
    355     return Maps.immutableEntry(key, value);
    356   }
    357 
    358   private void assertMapSize(Map<?, ?> map, int size) {
    359     assertEquals(size, map.size());
    360     if (size > 0) {
    361       assertFalse(map.isEmpty());
    362     } else {
    363       assertTrue(map.isEmpty());
    364     }
    365     assertCollectionSize(map.keySet(), size);
    366     assertCollectionSize(map.entrySet(), size);
    367     assertCollectionSize(map.values(), size);
    368   }
    369 
    370   private void assertCollectionSize(Collection<?> collection, int size) {
    371     assertEquals(size, collection.size());
    372     if (size > 0) {
    373       assertFalse(collection.isEmpty());
    374     } else {
    375       assertTrue(collection.isEmpty());
    376     }
    377     assertEquals(size, Iterables.size(collection));
    378     assertEquals(size, Iterators.size(collection.iterator()));
    379   }
    380 }
    381