• 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");
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 com.google.common.truth.Truth.assertThat;
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.testing.NullPointerTester;
29 import java.lang.Thread.UncaughtExceptionHandler;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.concurrent.ConcurrentMap;
33 import java.util.concurrent.CountDownLatch;
34 import java.util.concurrent.TimeUnit;
35 import java.util.concurrent.atomic.AtomicReference;
36 import junit.framework.TestCase;
37 
38 /** @author Charles Fry */
39 public class LocalLoadingCacheTest extends TestCase {
40 
makeCache( CacheBuilder<K, V> builder, CacheLoader<? super K, V> loader)41   private static <K, V> LocalLoadingCache<K, V> makeCache(
42       CacheBuilder<K, V> builder, CacheLoader<? super K, V> loader) {
43     return new LocalLoadingCache<>(builder, loader);
44   }
45 
createCacheBuilder()46   private CacheBuilder<Object, Object> createCacheBuilder() {
47     return CacheBuilder.newBuilder().recordStats();
48   }
49 
50   // constructor tests
51 
testComputingFunction()52   public void testComputingFunction() {
53     CacheLoader<Object, Object> loader =
54         new CacheLoader<Object, Object>() {
55           @Override
56           public Object load(Object from) {
57             return new Object();
58           }
59         };
60     LocalLoadingCache<Object, Object> cache = makeCache(createCacheBuilder(), loader);
61     assertSame(loader, cache.localCache.defaultLoader);
62   }
63 
64   // null parameters test
65 
testNullParameters()66   public void testNullParameters() throws Exception {
67     NullPointerTester tester = new NullPointerTester();
68     CacheLoader<Object, Object> loader = identityLoader();
69     tester.testAllPublicInstanceMethods(makeCache(createCacheBuilder(), loader));
70   }
71 
72   // stats tests
73 
testStats()74   public void testStats() {
75     CacheBuilder<Object, Object> builder = createCacheBuilder().concurrencyLevel(1).maximumSize(2);
76     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
77     assertEquals(EMPTY_STATS, cache.stats());
78 
79     Object one = new Object();
80     cache.getUnchecked(one);
81     CacheStats stats = cache.stats();
82     assertEquals(1, stats.requestCount());
83     assertEquals(0, stats.hitCount());
84     assertThat(stats.hitRate()).isEqualTo(0.0);
85     assertEquals(1, stats.missCount());
86     assertThat(stats.missRate()).isEqualTo(1.0);
87     assertEquals(1, stats.loadCount());
88     long totalLoadTime = stats.totalLoadTime();
89     assertTrue(totalLoadTime >= 0);
90     assertTrue(stats.averageLoadPenalty() >= 0.0);
91     assertEquals(0, stats.evictionCount());
92 
93     cache.getUnchecked(one);
94     stats = cache.stats();
95     assertEquals(2, stats.requestCount());
96     assertEquals(1, stats.hitCount());
97     assertThat(stats.hitRate()).isEqualTo(1.0 / 2);
98     assertEquals(1, stats.missCount());
99     assertThat(stats.missRate()).isEqualTo(1.0 / 2);
100     assertEquals(1, stats.loadCount());
101     assertEquals(0, stats.evictionCount());
102 
103     Object two = new Object();
104     cache.getUnchecked(two);
105     stats = cache.stats();
106     assertEquals(3, stats.requestCount());
107     assertEquals(1, stats.hitCount());
108     assertThat(stats.hitRate()).isEqualTo(1.0 / 3);
109     assertEquals(2, stats.missCount());
110     assertThat(stats.missRate()).isEqualTo(2.0 / 3);
111     assertEquals(2, stats.loadCount());
112     assertTrue(stats.totalLoadTime() >= totalLoadTime);
113     totalLoadTime = stats.totalLoadTime();
114     assertTrue(stats.averageLoadPenalty() >= 0.0);
115     assertEquals(0, stats.evictionCount());
116 
117     Object three = new Object();
118     cache.getUnchecked(three);
119     stats = cache.stats();
120     assertEquals(4, stats.requestCount());
121     assertEquals(1, stats.hitCount());
122     assertThat(stats.hitRate()).isEqualTo(1.0 / 4);
123     assertEquals(3, stats.missCount());
124     assertThat(stats.missRate()).isEqualTo(3.0 / 4);
125     assertEquals(3, stats.loadCount());
126     assertTrue(stats.totalLoadTime() >= totalLoadTime);
127     assertTrue(stats.averageLoadPenalty() >= 0.0);
128     assertEquals(1, stats.evictionCount());
129   }
130 
testStatsNoops()131   public void testStatsNoops() {
132     CacheBuilder<Object, Object> builder = createCacheBuilder().concurrencyLevel(1);
133     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
134     ConcurrentMap<Object, Object> map = cache.localCache; // modifiable map view
135     assertEquals(EMPTY_STATS, cache.stats());
136 
137     Object one = new Object();
138     assertNull(map.put(one, one));
139     assertSame(one, map.get(one));
140     assertTrue(map.containsKey(one));
141     assertTrue(map.containsValue(one));
142     Object two = new Object();
143     assertSame(one, map.replace(one, two));
144     assertTrue(map.containsKey(one));
145     assertFalse(map.containsValue(one));
146     Object three = new Object();
147     assertTrue(map.replace(one, two, three));
148     assertTrue(map.remove(one, three));
149     assertFalse(map.containsKey(one));
150     assertFalse(map.containsValue(one));
151     assertNull(map.putIfAbsent(two, three));
152     assertSame(three, map.remove(two));
153     assertNull(map.put(three, one));
154     assertNull(map.put(one, two));
155 
156     assertThat(map).containsEntry(three, one);
157     assertThat(map).containsEntry(one, two);
158 
159     // TODO(cgruber): Confirm with fry@ that this is a reasonable substitute.
160     // Set<Entry<Object, Object>> entries = map.entrySet();
161     // assertThat(entries).containsExactly(
162     //    Maps.immutableEntry(three, one), Maps.immutableEntry(one, two));
163     // Set<Object> keys = map.keySet();
164     // assertThat(keys).containsExactly(one, three);
165     // Collection<Object> values = map.values();
166     // assertThat(values).containsExactly(one, two);
167 
168     map.clear();
169 
170     assertEquals(EMPTY_STATS, cache.stats());
171   }
172 
testNoStats()173   public void testNoStats() {
174     CacheBuilder<Object, Object> builder =
175         CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(2);
176     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
177     assertEquals(EMPTY_STATS, cache.stats());
178 
179     Object one = new Object();
180     cache.getUnchecked(one);
181     assertEquals(EMPTY_STATS, cache.stats());
182 
183     cache.getUnchecked(one);
184     assertEquals(EMPTY_STATS, cache.stats());
185 
186     Object two = new Object();
187     cache.getUnchecked(two);
188     assertEquals(EMPTY_STATS, cache.stats());
189 
190     Object three = new Object();
191     cache.getUnchecked(three);
192     assertEquals(EMPTY_STATS, cache.stats());
193   }
194 
testRecordStats()195   public void testRecordStats() {
196     CacheBuilder<Object, Object> builder =
197         createCacheBuilder().recordStats().concurrencyLevel(1).maximumSize(2);
198     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
199     assertEquals(0, cache.stats().hitCount());
200     assertEquals(0, cache.stats().missCount());
201 
202     Object one = new Object();
203     cache.getUnchecked(one);
204     assertEquals(0, cache.stats().hitCount());
205     assertEquals(1, cache.stats().missCount());
206 
207     cache.getUnchecked(one);
208     assertEquals(1, cache.stats().hitCount());
209     assertEquals(1, cache.stats().missCount());
210 
211     Object two = new Object();
212     cache.getUnchecked(two);
213     assertEquals(1, cache.stats().hitCount());
214     assertEquals(2, cache.stats().missCount());
215 
216     Object three = new Object();
217     cache.getUnchecked(three);
218     assertEquals(1, cache.stats().hitCount());
219     assertEquals(3, cache.stats().missCount());
220   }
221 
222   // asMap tests
223 
testAsMap()224   public void testAsMap() {
225     CacheBuilder<Object, Object> builder = createCacheBuilder();
226     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
227     assertEquals(EMPTY_STATS, cache.stats());
228 
229     Object one = new Object();
230     Object two = new Object();
231     Object three = new Object();
232 
233     ConcurrentMap<Object, Object> map = cache.asMap();
234     assertNull(map.put(one, two));
235     assertSame(two, map.get(one));
236     map.putAll(ImmutableMap.of(two, three));
237     assertSame(three, map.get(two));
238     assertSame(two, map.putIfAbsent(one, three));
239     assertSame(two, map.get(one));
240     assertNull(map.putIfAbsent(three, one));
241     assertSame(one, map.get(three));
242     assertSame(two, map.replace(one, three));
243     assertSame(three, map.get(one));
244     assertFalse(map.replace(one, two, three));
245     assertSame(three, map.get(one));
246     assertTrue(map.replace(one, three, two));
247     assertSame(two, map.get(one));
248     assertEquals(3, map.size());
249 
250     map.clear();
251     assertTrue(map.isEmpty());
252     assertEquals(0, map.size());
253 
254     cache.getUnchecked(one);
255     assertEquals(1, map.size());
256     assertSame(one, map.get(one));
257     assertTrue(map.containsKey(one));
258     assertTrue(map.containsValue(one));
259     assertSame(one, map.remove(one));
260     assertEquals(0, map.size());
261 
262     cache.getUnchecked(one);
263     assertEquals(1, map.size());
264     assertFalse(map.remove(one, two));
265     assertTrue(map.remove(one, one));
266     assertEquals(0, map.size());
267 
268     cache.getUnchecked(one);
269     Map<Object, Object> newMap = ImmutableMap.of(one, one);
270     assertEquals(newMap, map);
271     assertEquals(newMap.entrySet(), map.entrySet());
272     assertEquals(newMap.keySet(), map.keySet());
273     Set<Object> expectedValues = ImmutableSet.of(one);
274     Set<Object> actualValues = ImmutableSet.copyOf(map.values());
275     assertEquals(expectedValues, actualValues);
276   }
277 
278   /** Lookups on the map view shouldn't impact the recency queue. */
testAsMapRecency()279   public void testAsMapRecency() {
280     CacheBuilder<Object, Object> builder =
281         createCacheBuilder().concurrencyLevel(1).maximumSize(SMALL_MAX_SIZE);
282     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
283     Segment<Object, Object> segment = cache.localCache.segments[0];
284     ConcurrentMap<Object, Object> map = cache.asMap();
285 
286     Object one = new Object();
287     assertSame(one, cache.getUnchecked(one));
288     assertTrue(segment.recencyQueue.isEmpty());
289     assertSame(one, map.get(one));
290     assertSame(one, segment.recencyQueue.peek().getKey());
291     assertSame(one, cache.getUnchecked(one));
292     assertFalse(segment.recencyQueue.isEmpty());
293   }
294 
testRecursiveComputation()295   public void testRecursiveComputation() throws InterruptedException {
296     final AtomicReference<LoadingCache<Integer, String>> cacheRef = new AtomicReference<>();
297     CacheLoader<Integer, String> recursiveLoader =
298         new CacheLoader<Integer, String>() {
299           @Override
300           public String load(Integer key) {
301             if (key > 0) {
302               return key + ", " + cacheRef.get().getUnchecked(key - 1);
303             } else {
304               return "0";
305             }
306           }
307         };
308 
309     LoadingCache<Integer, String> recursiveCache =
310         CacheBuilder.newBuilder().weakKeys().weakValues().build(recursiveLoader);
311     cacheRef.set(recursiveCache);
312     assertEquals("3, 2, 1, 0", recursiveCache.getUnchecked(3));
313 
314     recursiveLoader =
315         new CacheLoader<Integer, String>() {
316           @Override
317           public String load(Integer key) {
318             return cacheRef.get().getUnchecked(key);
319           }
320         };
321 
322     recursiveCache = CacheBuilder.newBuilder().weakKeys().weakValues().build(recursiveLoader);
323     cacheRef.set(recursiveCache);
324 
325     // tells the test when the computation has completed
326     final CountDownLatch doneSignal = new CountDownLatch(1);
327 
328     Thread thread =
329         new Thread() {
330           @Override
331           public void run() {
332             try {
333               cacheRef.get().getUnchecked(3);
334             } finally {
335               doneSignal.countDown();
336             }
337           }
338         };
339     thread.setUncaughtExceptionHandler(
340         new UncaughtExceptionHandler() {
341           @Override
342           public void uncaughtException(Thread t, Throwable e) {}
343         });
344     thread.start();
345 
346     boolean done = doneSignal.await(1, TimeUnit.SECONDS);
347     if (!done) {
348       StringBuilder builder = new StringBuilder();
349       for (StackTraceElement trace : thread.getStackTrace()) {
350         builder.append("\tat ").append(trace).append('\n');
351       }
352       fail(builder.toString());
353     }
354   }
355 }
356