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