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 assertThat(stats.hitRate()).isEqualTo(0.0); 85 assertEquals(1, stats.missCount()); 86 assertThat(stats.missRate()).isEqualTo(1.0); 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 assertThat(stats.hitRate()).isEqualTo(1.0 / 2); 98 assertEquals(1, stats.missCount()); 99 assertThat(stats.missRate()).isEqualTo(1.0 / 2); 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 assertThat(stats.hitRate()).isEqualTo(1.0 / 3); 109 assertEquals(2, stats.missCount()); 110 assertThat(stats.missRate()).isEqualTo(2.0 / 3); 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 assertThat(stats.hitRate()).isEqualTo(1.0 / 4); 123 assertEquals(3, stats.missCount()); 124 assertThat(stats.missRate()).isEqualTo(3.0 / 4); 125 assertEquals(3, stats.loadCount()); 126 assertTrue(stats.totalLoadTime() >= totalLoadTime); 127 assertTrue(stats.averageLoadPenalty() >= 0.0); 128 assertEquals(1, stats.evictionCount()); 129 } 130 testStatsNoops()131 public void testStatsNoops() { 132 CacheBuilder<Object, Object> builder = createCacheBuilder().concurrencyLevel(1); 133 LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader()); 134 ConcurrentMap<Object, Object> map = cache.localCache; // modifiable map view 135 assertEquals(EMPTY_STATS, cache.stats()); 136 137 Object one = new Object(); 138 assertNull(map.put(one, one)); 139 assertSame(one, map.get(one)); 140 assertTrue(map.containsKey(one)); 141 assertTrue(map.containsValue(one)); 142 Object two = new Object(); 143 assertSame(one, map.replace(one, two)); 144 assertTrue(map.containsKey(one)); 145 assertFalse(map.containsValue(one)); 146 Object three = new Object(); 147 assertTrue(map.replace(one, two, three)); 148 assertTrue(map.remove(one, three)); 149 assertFalse(map.containsKey(one)); 150 assertFalse(map.containsValue(one)); 151 assertNull(map.putIfAbsent(two, three)); 152 assertSame(three, map.remove(two)); 153 assertNull(map.put(three, one)); 154 assertNull(map.put(one, two)); 155 156 assertThat(map).containsEntry(three, one); 157 assertThat(map).containsEntry(one, two); 158 159 // TODO(cgruber): Confirm with fry@ that this is a reasonable substitute. 160 // Set<Entry<Object, Object>> entries = map.entrySet(); 161 // assertThat(entries).containsExactly( 162 // Maps.immutableEntry(three, one), Maps.immutableEntry(one, two)); 163 // Set<Object> keys = map.keySet(); 164 // assertThat(keys).containsExactly(one, three); 165 // Collection<Object> values = map.values(); 166 // assertThat(values).containsExactly(one, two); 167 168 map.clear(); 169 170 assertEquals(EMPTY_STATS, cache.stats()); 171 } 172 testNoStats()173 public void testNoStats() { 174 CacheBuilder<Object, Object> builder = 175 CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(2); 176 LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader()); 177 assertEquals(EMPTY_STATS, cache.stats()); 178 179 Object one = new Object(); 180 cache.getUnchecked(one); 181 assertEquals(EMPTY_STATS, cache.stats()); 182 183 cache.getUnchecked(one); 184 assertEquals(EMPTY_STATS, cache.stats()); 185 186 Object two = new Object(); 187 cache.getUnchecked(two); 188 assertEquals(EMPTY_STATS, cache.stats()); 189 190 Object three = new Object(); 191 cache.getUnchecked(three); 192 assertEquals(EMPTY_STATS, cache.stats()); 193 } 194 testRecordStats()195 public void testRecordStats() { 196 CacheBuilder<Object, Object> builder = 197 createCacheBuilder().recordStats().concurrencyLevel(1).maximumSize(2); 198 LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader()); 199 assertEquals(0, cache.stats().hitCount()); 200 assertEquals(0, cache.stats().missCount()); 201 202 Object one = new Object(); 203 cache.getUnchecked(one); 204 assertEquals(0, cache.stats().hitCount()); 205 assertEquals(1, cache.stats().missCount()); 206 207 cache.getUnchecked(one); 208 assertEquals(1, cache.stats().hitCount()); 209 assertEquals(1, cache.stats().missCount()); 210 211 Object two = new Object(); 212 cache.getUnchecked(two); 213 assertEquals(1, cache.stats().hitCount()); 214 assertEquals(2, cache.stats().missCount()); 215 216 Object three = new Object(); 217 cache.getUnchecked(three); 218 assertEquals(1, cache.stats().hitCount()); 219 assertEquals(3, cache.stats().missCount()); 220 } 221 222 // asMap tests 223 testAsMap()224 public void testAsMap() { 225 CacheBuilder<Object, Object> builder = createCacheBuilder(); 226 LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader()); 227 assertEquals(EMPTY_STATS, cache.stats()); 228 229 Object one = new Object(); 230 Object two = new Object(); 231 Object three = new Object(); 232 233 ConcurrentMap<Object, Object> map = cache.asMap(); 234 assertNull(map.put(one, two)); 235 assertSame(two, map.get(one)); 236 map.putAll(ImmutableMap.of(two, three)); 237 assertSame(three, map.get(two)); 238 assertSame(two, map.putIfAbsent(one, three)); 239 assertSame(two, map.get(one)); 240 assertNull(map.putIfAbsent(three, one)); 241 assertSame(one, map.get(three)); 242 assertSame(two, map.replace(one, three)); 243 assertSame(three, map.get(one)); 244 assertFalse(map.replace(one, two, three)); 245 assertSame(three, map.get(one)); 246 assertTrue(map.replace(one, three, two)); 247 assertSame(two, map.get(one)); 248 assertEquals(3, map.size()); 249 250 map.clear(); 251 assertTrue(map.isEmpty()); 252 assertEquals(0, map.size()); 253 254 cache.getUnchecked(one); 255 assertEquals(1, map.size()); 256 assertSame(one, map.get(one)); 257 assertTrue(map.containsKey(one)); 258 assertTrue(map.containsValue(one)); 259 assertSame(one, map.remove(one)); 260 assertEquals(0, map.size()); 261 262 cache.getUnchecked(one); 263 assertEquals(1, map.size()); 264 assertFalse(map.remove(one, two)); 265 assertTrue(map.remove(one, one)); 266 assertEquals(0, map.size()); 267 268 cache.getUnchecked(one); 269 Map<Object, Object> newMap = ImmutableMap.of(one, one); 270 assertEquals(newMap, map); 271 assertEquals(newMap.entrySet(), map.entrySet()); 272 assertEquals(newMap.keySet(), map.keySet()); 273 Set<Object> expectedValues = ImmutableSet.of(one); 274 Set<Object> actualValues = ImmutableSet.copyOf(map.values()); 275 assertEquals(expectedValues, actualValues); 276 } 277 278 /** Lookups on the map view shouldn't impact the recency queue. */ testAsMapRecency()279 public void testAsMapRecency() { 280 CacheBuilder<Object, Object> builder = 281 createCacheBuilder().concurrencyLevel(1).maximumSize(SMALL_MAX_SIZE); 282 LocalLoadingCache<Object, Object> cache = makeCache(builder, identityLoader()); 283 Segment<Object, Object> segment = cache.localCache.segments[0]; 284 ConcurrentMap<Object, Object> map = cache.asMap(); 285 286 Object one = new Object(); 287 assertSame(one, cache.getUnchecked(one)); 288 assertTrue(segment.recencyQueue.isEmpty()); 289 assertSame(one, map.get(one)); 290 assertSame(one, segment.recencyQueue.peek().getKey()); 291 assertSame(one, cache.getUnchecked(one)); 292 assertFalse(segment.recencyQueue.isEmpty()); 293 } 294 testRecursiveComputation()295 public void testRecursiveComputation() throws InterruptedException { 296 final AtomicReference<LoadingCache<Integer, String>> cacheRef = new AtomicReference<>(); 297 CacheLoader<Integer, String> recursiveLoader = 298 new CacheLoader<Integer, String>() { 299 @Override 300 public String load(Integer key) { 301 if (key > 0) { 302 return key + ", " + cacheRef.get().getUnchecked(key - 1); 303 } else { 304 return "0"; 305 } 306 } 307 }; 308 309 LoadingCache<Integer, String> recursiveCache = 310 CacheBuilder.newBuilder().weakKeys().weakValues().build(recursiveLoader); 311 cacheRef.set(recursiveCache); 312 assertEquals("3, 2, 1, 0", recursiveCache.getUnchecked(3)); 313 314 recursiveLoader = 315 new CacheLoader<Integer, String>() { 316 @Override 317 public String load(Integer key) { 318 return cacheRef.get().getUnchecked(key); 319 } 320 }; 321 322 recursiveCache = CacheBuilder.newBuilder().weakKeys().weakValues().build(recursiveLoader); 323 cacheRef.set(recursiveCache); 324 325 // tells the test when the computation has completed 326 final CountDownLatch doneSignal = new CountDownLatch(1); 327 328 Thread thread = 329 new Thread() { 330 @Override 331 public void run() { 332 try { 333 cacheRef.get().getUnchecked(3); 334 } finally { 335 doneSignal.countDown(); 336 } 337 } 338 }; 339 thread.setUncaughtExceptionHandler( 340 new UncaughtExceptionHandler() { 341 @Override 342 public void uncaughtException(Thread t, Throwable e) {} 343 }); 344 thread.start(); 345 346 boolean done = doneSignal.await(1, TimeUnit.SECONDS); 347 if (!done) { 348 StringBuilder builder = new StringBuilder(); 349 for (StackTraceElement trace : thread.getStackTrace()) { 350 builder.append("\tat ").append(trace).append('\n'); 351 } 352 fail(builder.toString()); 353 } 354 } 355 } 356