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