LocalLoadingCacheTest.java revision 1d580d0f6ee4f21eb309ba7b509d2c6d671c4044
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
17package com.google.common.cache;
18
19import static com.google.common.cache.CacheBuilder.EMPTY_STATS;
20import static com.google.common.cache.LocalCacheTest.SMALL_MAX_SIZE;
21import static com.google.common.cache.TestingCacheLoaders.identityLoader;
22import static org.junit.contrib.truth.Truth.ASSERT;
23
24import com.google.common.cache.LocalCache.LocalLoadingCache;
25import com.google.common.cache.LocalCache.Segment;
26import com.google.common.collect.ImmutableMap;
27import com.google.common.collect.ImmutableSet;
28import com.google.common.collect.Maps;
29import com.google.common.testing.NullPointerTester;
30import com.google.common.util.concurrent.Callables;
31
32import junit.framework.TestCase;
33
34import java.lang.Thread.UncaughtExceptionHandler;
35import java.util.Collection;
36import java.util.Map;
37import java.util.Set;
38import java.util.concurrent.Callable;
39import java.util.concurrent.ConcurrentMap;
40import java.util.concurrent.CountDownLatch;
41import java.util.concurrent.TimeUnit;
42import java.util.concurrent.atomic.AtomicReference;
43
44/**
45 * @author Charles Fry
46 */
47public class LocalLoadingCacheTest extends TestCase {
48
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
54  private CacheBuilder<Object, Object> createCacheBuilder() {
55    return new CacheBuilder<Object, Object>();
56  }
57
58  // constructor tests
59
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
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
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
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
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
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   */
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
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