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 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