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