• 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     assertEquals(0.0, stats.hitRate());
85     assertEquals(1, stats.missCount());
86     assertEquals(1.0, stats.missRate());
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     assertEquals(1.0 / 2, stats.hitRate());
98     assertEquals(1, stats.missCount());
99     assertEquals(1.0 / 2, stats.missRate());
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     assertEquals(1.0 / 3, stats.hitRate());
109     assertEquals(2, stats.missCount());
110     assertEquals(2.0 / 3, stats.missRate());
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     assertEquals(1.0 / 4, stats.hitRate());
123     assertEquals(3, stats.missCount());
124     assertEquals(3.0 / 4, stats.missRate());
125     assertEquals(3, stats.loadCount());
126     assertTrue(stats.totalLoadTime() >= totalLoadTime);
127     totalLoadTime = stats.totalLoadTime();
128     assertTrue(stats.averageLoadPenalty() >= 0.0);
129     assertEquals(1, stats.evictionCount());
130   }
131 
testStatsNoops()132   public void testStatsNoops() {
133     CacheBuilder<Object, Object> builder = createCacheBuilder().concurrencyLevel(1);
134     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
135     ConcurrentMap<Object, Object> map = cache.localCache; // modifiable map view
136     assertEquals(EMPTY_STATS, cache.stats());
137 
138     Object one = new Object();
139     assertNull(map.put(one, one));
140     assertSame(one, map.get(one));
141     assertTrue(map.containsKey(one));
142     assertTrue(map.containsValue(one));
143     Object two = new Object();
144     assertSame(one, map.replace(one, two));
145     assertTrue(map.containsKey(one));
146     assertFalse(map.containsValue(one));
147     Object three = new Object();
148     assertTrue(map.replace(one, two, three));
149     assertTrue(map.remove(one, three));
150     assertFalse(map.containsKey(one));
151     assertFalse(map.containsValue(one));
152     assertNull(map.putIfAbsent(two, three));
153     assertSame(three, map.remove(two));
154     assertNull(map.put(three, one));
155     assertNull(map.put(one, two));
156 
157     assertThat(map).containsEntry(three, one);
158     assertThat(map).containsEntry(one, two);
159 
160     // TODO(user): Confirm with fry@ that this is a reasonable substitute.
161     // Set<Entry<Object, Object>> entries = map.entrySet();
162     // assertThat(entries).containsExactly(
163     //    Maps.immutableEntry(three, one), Maps.immutableEntry(one, two));
164     // Set<Object> keys = map.keySet();
165     // assertThat(keys).containsExactly(one, three);
166     // Collection<Object> values = map.values();
167     // assertThat(values).containsExactly(one, two);
168 
169     map.clear();
170 
171     assertEquals(EMPTY_STATS, cache.stats());
172   }
173 
testNoStats()174   public void testNoStats() {
175     CacheBuilder<Object, Object> builder =
176         CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(2);
177     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
178     assertEquals(EMPTY_STATS, cache.stats());
179 
180     Object one = new Object();
181     cache.getUnchecked(one);
182     assertEquals(EMPTY_STATS, cache.stats());
183 
184     cache.getUnchecked(one);
185     assertEquals(EMPTY_STATS, cache.stats());
186 
187     Object two = new Object();
188     cache.getUnchecked(two);
189     assertEquals(EMPTY_STATS, cache.stats());
190 
191     Object three = new Object();
192     cache.getUnchecked(three);
193     assertEquals(EMPTY_STATS, cache.stats());
194   }
195 
testRecordStats()196   public void testRecordStats() {
197     CacheBuilder<Object, Object> builder =
198         createCacheBuilder().recordStats().concurrencyLevel(1).maximumSize(2);
199     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
200     assertEquals(0, cache.stats().hitCount());
201     assertEquals(0, cache.stats().missCount());
202 
203     Object one = new Object();
204     cache.getUnchecked(one);
205     assertEquals(0, cache.stats().hitCount());
206     assertEquals(1, cache.stats().missCount());
207 
208     cache.getUnchecked(one);
209     assertEquals(1, cache.stats().hitCount());
210     assertEquals(1, cache.stats().missCount());
211 
212     Object two = new Object();
213     cache.getUnchecked(two);
214     assertEquals(1, cache.stats().hitCount());
215     assertEquals(2, cache.stats().missCount());
216 
217     Object three = new Object();
218     cache.getUnchecked(three);
219     assertEquals(1, cache.stats().hitCount());
220     assertEquals(3, cache.stats().missCount());
221   }
222 
223   // asMap tests
224 
testAsMap()225   public void testAsMap() {
226     CacheBuilder<Object, Object> builder = createCacheBuilder();
227     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
228     assertEquals(EMPTY_STATS, cache.stats());
229 
230     Object one = new Object();
231     Object two = new Object();
232     Object three = new Object();
233 
234     ConcurrentMap<Object, Object> map = cache.asMap();
235     assertNull(map.put(one, two));
236     assertSame(two, map.get(one));
237     map.putAll(ImmutableMap.of(two, three));
238     assertSame(three, map.get(two));
239     assertSame(two, map.putIfAbsent(one, three));
240     assertSame(two, map.get(one));
241     assertNull(map.putIfAbsent(three, one));
242     assertSame(one, map.get(three));
243     assertSame(two, map.replace(one, three));
244     assertSame(three, map.get(one));
245     assertFalse(map.replace(one, two, three));
246     assertSame(three, map.get(one));
247     assertTrue(map.replace(one, three, two));
248     assertSame(two, map.get(one));
249     assertEquals(3, map.size());
250 
251     map.clear();
252     assertTrue(map.isEmpty());
253     assertEquals(0, map.size());
254 
255     cache.getUnchecked(one);
256     assertEquals(1, map.size());
257     assertSame(one, map.get(one));
258     assertTrue(map.containsKey(one));
259     assertTrue(map.containsValue(one));
260     assertSame(one, map.remove(one));
261     assertEquals(0, map.size());
262 
263     cache.getUnchecked(one);
264     assertEquals(1, map.size());
265     assertFalse(map.remove(one, two));
266     assertTrue(map.remove(one, one));
267     assertEquals(0, map.size());
268 
269     cache.getUnchecked(one);
270     Map<Object, Object> newMap = ImmutableMap.of(one, one);
271     assertEquals(newMap, map);
272     assertEquals(newMap.entrySet(), map.entrySet());
273     assertEquals(newMap.keySet(), map.keySet());
274     Set<Object> expectedValues = ImmutableSet.of(one);
275     Set<Object> actualValues = ImmutableSet.copyOf(map.values());
276     assertEquals(expectedValues, actualValues);
277   }
278 
279   /** Lookups on the map view shouldn't impact the recency queue. */
testAsMapRecency()280   public void testAsMapRecency() {
281     CacheBuilder<Object, Object> builder =
282         createCacheBuilder().concurrencyLevel(1).maximumSize(SMALL_MAX_SIZE);
283     LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader());
284     Segment<Object, Object> segment = cache.localCache.segments[0];
285     ConcurrentMap<Object, Object> map = cache.asMap();
286 
287     Object one = new Object();
288     assertSame(one, cache.getUnchecked(one));
289     assertTrue(segment.recencyQueue.isEmpty());
290     assertSame(one, map.get(one));
291     assertSame(one, segment.recencyQueue.peek().getKey());
292     assertSame(one, cache.getUnchecked(one));
293     assertFalse(segment.recencyQueue.isEmpty());
294   }
295 
296 
testRecursiveComputation()297   public void testRecursiveComputation() throws InterruptedException {
298     final AtomicReference<LoadingCache<Integer, String>> cacheRef = new AtomicReference<>();
299     CacheLoader<Integer, String> recursiveLoader =
300         new CacheLoader<Integer, String>() {
301           @Override
302           public String load(Integer key) {
303             if (key > 0) {
304               return key + ", " + cacheRef.get().getUnchecked(key - 1);
305             } else {
306               return "0";
307             }
308           }
309         };
310 
311     LoadingCache<Integer, String> recursiveCache =
312         CacheBuilder.newBuilder().weakKeys().weakValues().build(recursiveLoader);
313     cacheRef.set(recursiveCache);
314     assertEquals("3, 2, 1, 0", recursiveCache.getUnchecked(3));
315 
316     recursiveLoader =
317         new CacheLoader<Integer, String>() {
318           @Override
319           public String load(Integer key) {
320             return cacheRef.get().getUnchecked(key);
321           }
322         };
323 
324     recursiveCache = CacheBuilder.newBuilder().weakKeys().weakValues().build(recursiveLoader);
325     cacheRef.set(recursiveCache);
326 
327     // tells the test when the compution has completed
328     final CountDownLatch doneSignal = new CountDownLatch(1);
329 
330     Thread thread =
331         new Thread() {
332           @Override
333           public void run() {
334             try {
335               cacheRef.get().getUnchecked(3);
336             } finally {
337               doneSignal.countDown();
338             }
339           }
340         };
341     thread.setUncaughtExceptionHandler(
342         new UncaughtExceptionHandler() {
343           @Override
344           public void uncaughtException(Thread t, Throwable e) {}
345         });
346     thread.start();
347 
348     boolean done = doneSignal.await(1, TimeUnit.SECONDS);
349     if (!done) {
350       StringBuilder builder = new StringBuilder();
351       for (StackTraceElement trace : thread.getStackTrace()) {
352         builder.append("\tat ").append(trace).append('\n');
353       }
354       fail(builder.toString());
355     }
356   }
357 }
358