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