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