1 /* 2 * Copyright (C) 2009 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.TestingCacheLoaders.constantLoader; 20 import static com.google.common.cache.TestingCacheLoaders.identityLoader; 21 import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener; 22 import static com.google.common.cache.TestingRemovalListeners.nullRemovalListener; 23 import static com.google.common.cache.TestingRemovalListeners.queuingRemovalListener; 24 import static com.google.common.cache.TestingWeighers.constantWeigher; 25 import static com.google.common.truth.Truth.assertThat; 26 import static java.util.concurrent.TimeUnit.MILLISECONDS; 27 import static java.util.concurrent.TimeUnit.NANOSECONDS; 28 import static java.util.concurrent.TimeUnit.SECONDS; 29 import static org.junit.Assert.assertThrows; 30 31 import com.google.common.annotations.GwtCompatible; 32 import com.google.common.annotations.GwtIncompatible; 33 import com.google.common.base.Ticker; 34 import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener; 35 import com.google.common.cache.TestingRemovalListeners.QueuingRemovalListener; 36 import com.google.common.collect.Maps; 37 import com.google.common.collect.Sets; 38 import com.google.common.testing.NullPointerTester; 39 import java.util.Map; 40 import java.util.Random; 41 import java.util.Set; 42 import java.util.concurrent.CountDownLatch; 43 import java.util.concurrent.ExecutorService; 44 import java.util.concurrent.Executors; 45 import java.util.concurrent.Future; 46 import java.util.concurrent.atomic.AtomicBoolean; 47 import java.util.concurrent.atomic.AtomicInteger; 48 import junit.framework.TestCase; 49 50 /** Unit tests for CacheBuilder. */ 51 @GwtCompatible(emulated = true) 52 public class CacheBuilderTest extends TestCase { 53 testNewBuilder()54 public void testNewBuilder() { 55 CacheLoader<Object, Integer> loader = constantLoader(1); 56 57 LoadingCache<String, Integer> cache = 58 CacheBuilder.newBuilder().removalListener(countingRemovalListener()).build(loader); 59 60 assertEquals(Integer.valueOf(1), cache.getUnchecked("one")); 61 assertEquals(1, cache.size()); 62 } 63 testInitialCapacity_negative()64 public void testInitialCapacity_negative() { 65 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 66 try { 67 builder.initialCapacity(-1); 68 fail(); 69 } catch (IllegalArgumentException expected) { 70 } 71 } 72 testInitialCapacity_setTwice()73 public void testInitialCapacity_setTwice() { 74 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().initialCapacity(16); 75 try { 76 // even to the same value is not allowed 77 builder.initialCapacity(16); 78 fail(); 79 } catch (IllegalStateException expected) { 80 } 81 } 82 83 @GwtIncompatible // CacheTesting testInitialCapacity_small()84 public void testInitialCapacity_small() { 85 LoadingCache<?, ?> cache = CacheBuilder.newBuilder().initialCapacity(5).build(identityLoader()); 86 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache); 87 88 assertThat(map.segments).hasLength(4); 89 assertEquals(2, map.segments[0].table.length()); 90 assertEquals(2, map.segments[1].table.length()); 91 assertEquals(2, map.segments[2].table.length()); 92 assertEquals(2, map.segments[3].table.length()); 93 } 94 95 @GwtIncompatible // CacheTesting testInitialCapacity_smallest()96 public void testInitialCapacity_smallest() { 97 LoadingCache<?, ?> cache = CacheBuilder.newBuilder().initialCapacity(0).build(identityLoader()); 98 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache); 99 100 assertThat(map.segments).hasLength(4); 101 // 1 is as low as it goes, not 0. it feels dirty to know this/test this. 102 assertEquals(1, map.segments[0].table.length()); 103 assertEquals(1, map.segments[1].table.length()); 104 assertEquals(1, map.segments[2].table.length()); 105 assertEquals(1, map.segments[3].table.length()); 106 } 107 testInitialCapacity_large()108 public void testInitialCapacity_large() { 109 CacheBuilder.newBuilder().initialCapacity(Integer.MAX_VALUE); 110 // that the builder didn't blow up is enough; 111 // don't actually create this monster! 112 } 113 testConcurrencyLevel_zero()114 public void testConcurrencyLevel_zero() { 115 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 116 try { 117 builder.concurrencyLevel(0); 118 fail(); 119 } catch (IllegalArgumentException expected) { 120 } 121 } 122 testConcurrencyLevel_setTwice()123 public void testConcurrencyLevel_setTwice() { 124 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().concurrencyLevel(16); 125 try { 126 // even to the same value is not allowed 127 builder.concurrencyLevel(16); 128 fail(); 129 } catch (IllegalStateException expected) { 130 } 131 } 132 133 @GwtIncompatible // CacheTesting testConcurrencyLevel_small()134 public void testConcurrencyLevel_small() { 135 LoadingCache<?, ?> cache = 136 CacheBuilder.newBuilder().concurrencyLevel(1).build(identityLoader()); 137 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache); 138 assertThat(map.segments).hasLength(1); 139 } 140 testConcurrencyLevel_large()141 public void testConcurrencyLevel_large() { 142 CacheBuilder.newBuilder().concurrencyLevel(Integer.MAX_VALUE); 143 // don't actually build this beast 144 } 145 testMaximumSize_negative()146 public void testMaximumSize_negative() { 147 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 148 try { 149 builder.maximumSize(-1); 150 fail(); 151 } catch (IllegalArgumentException expected) { 152 } 153 } 154 testMaximumSize_setTwice()155 public void testMaximumSize_setTwice() { 156 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumSize(16); 157 try { 158 // even to the same value is not allowed 159 builder.maximumSize(16); 160 fail(); 161 } catch (IllegalStateException expected) { 162 } 163 } 164 165 @GwtIncompatible // maximumWeight testMaximumSize_andWeight()166 public void testMaximumSize_andWeight() { 167 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumSize(16); 168 assertThrows(IllegalStateException.class, () -> builder.maximumWeight(16)); 169 } 170 171 @GwtIncompatible // digs into internals of the non-GWT implementation testMaximumSize_largerThanInt()172 public void testMaximumSize_largerThanInt() { 173 CacheBuilder<Object, Object> builder = 174 CacheBuilder.newBuilder().initialCapacity(512).maximumSize(Long.MAX_VALUE); 175 LocalCache<?, ?> cache = ((LocalCache.LocalManualCache<?, ?>) builder.build()).localCache; 176 assertThat(cache.segments.length * cache.segments[0].table.length()).isEqualTo(512); 177 } 178 179 @GwtIncompatible // maximumWeight testMaximumWeight_negative()180 public void testMaximumWeight_negative() { 181 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 182 assertThrows(IllegalArgumentException.class, () -> builder.maximumWeight(-1)); 183 } 184 185 @GwtIncompatible // maximumWeight testMaximumWeight_setTwice()186 public void testMaximumWeight_setTwice() { 187 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumWeight(16); 188 assertThrows(IllegalStateException.class, () -> builder.maximumWeight(16)); 189 assertThrows(IllegalStateException.class, () -> builder.maximumSize(16)); 190 } 191 192 @GwtIncompatible // maximumWeight testMaximumWeight_withoutWeigher()193 public void testMaximumWeight_withoutWeigher() { 194 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumWeight(1); 195 assertThrows(IllegalStateException.class, () -> builder.build(identityLoader())); 196 } 197 198 @GwtIncompatible // weigher testWeigher_withoutMaximumWeight()199 public void testWeigher_withoutMaximumWeight() { 200 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().weigher(constantWeigher(42)); 201 assertThrows(IllegalStateException.class, () -> builder.build(identityLoader())); 202 } 203 204 @GwtIncompatible // weigher testWeigher_withMaximumSize()205 public void testWeigher_withMaximumSize() { 206 assertThrows( 207 IllegalStateException.class, 208 () -> CacheBuilder.newBuilder().weigher(constantWeigher(42)).maximumSize(1)); 209 assertThrows( 210 IllegalStateException.class, 211 () -> CacheBuilder.newBuilder().maximumSize(1).weigher(constantWeigher(42))); 212 } 213 214 @GwtIncompatible // weakKeys testKeyStrengthSetTwice()215 public void testKeyStrengthSetTwice() { 216 CacheBuilder<Object, Object> builder1 = CacheBuilder.newBuilder().weakKeys(); 217 assertThrows(IllegalStateException.class, () -> builder1.weakKeys()); 218 } 219 220 @GwtIncompatible // weakValues testValueStrengthSetTwice()221 public void testValueStrengthSetTwice() { 222 CacheBuilder<Object, Object> builder1 = CacheBuilder.newBuilder().weakValues(); 223 assertThrows(IllegalStateException.class, () -> builder1.weakValues()); 224 assertThrows(IllegalStateException.class, () -> builder1.softValues()); 225 226 CacheBuilder<Object, Object> builder2 = CacheBuilder.newBuilder().softValues(); 227 assertThrows(IllegalStateException.class, () -> builder2.softValues()); 228 assertThrows(IllegalStateException.class, () -> builder2.weakValues()); 229 } 230 231 @GwtIncompatible // java.time.Duration testLargeDurationsAreOk()232 public void testLargeDurationsAreOk() { 233 java.time.Duration threeHundredYears = java.time.Duration.ofDays(365 * 300); 234 CacheBuilder<Object, Object> builder = 235 CacheBuilder.newBuilder() 236 .expireAfterWrite(threeHundredYears) 237 .expireAfterAccess(threeHundredYears) 238 .refreshAfterWrite(threeHundredYears); 239 } 240 testTimeToLive_negative()241 public void testTimeToLive_negative() { 242 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 243 try { 244 builder.expireAfterWrite(-1, SECONDS); 245 fail(); 246 } catch (IllegalArgumentException expected) { 247 } 248 } 249 250 @GwtIncompatible // java.time.Duration testTimeToLive_negative_duration()251 public void testTimeToLive_negative_duration() { 252 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 253 assertThrows( 254 IllegalArgumentException.class, 255 () -> builder.expireAfterWrite(java.time.Duration.ofSeconds(-1))); 256 } 257 258 @SuppressWarnings("ReturnValueIgnored") testTimeToLive_small()259 public void testTimeToLive_small() { 260 CacheBuilder.newBuilder().expireAfterWrite(1, NANOSECONDS).build(identityLoader()); 261 // well, it didn't blow up. 262 } 263 testTimeToLive_setTwice()264 public void testTimeToLive_setTwice() { 265 CacheBuilder<Object, Object> builder = 266 CacheBuilder.newBuilder().expireAfterWrite(3600, SECONDS); 267 try { 268 // even to the same value is not allowed 269 builder.expireAfterWrite(3600, SECONDS); 270 fail(); 271 } catch (IllegalStateException expected) { 272 } 273 } 274 275 @GwtIncompatible // java.time.Duration testTimeToLive_setTwice_duration()276 public void testTimeToLive_setTwice_duration() { 277 CacheBuilder<Object, Object> builder = 278 CacheBuilder.newBuilder().expireAfterWrite(java.time.Duration.ofSeconds(3600)); 279 assertThrows( 280 IllegalStateException.class, 281 () -> builder.expireAfterWrite(java.time.Duration.ofSeconds(3600))); 282 } 283 testTimeToIdle_negative()284 public void testTimeToIdle_negative() { 285 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 286 try { 287 builder.expireAfterAccess(-1, SECONDS); 288 fail(); 289 } catch (IllegalArgumentException expected) { 290 } 291 } 292 293 @GwtIncompatible // java.time.Duration testTimeToIdle_negative_duration()294 public void testTimeToIdle_negative_duration() { 295 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 296 assertThrows( 297 IllegalArgumentException.class, 298 () -> builder.expireAfterAccess(java.time.Duration.ofSeconds(-1))); 299 } 300 301 @SuppressWarnings("ReturnValueIgnored") testTimeToIdle_small()302 public void testTimeToIdle_small() { 303 CacheBuilder.newBuilder().expireAfterAccess(1, NANOSECONDS).build(identityLoader()); 304 // well, it didn't blow up. 305 } 306 testTimeToIdle_setTwice()307 public void testTimeToIdle_setTwice() { 308 CacheBuilder<Object, Object> builder = 309 CacheBuilder.newBuilder().expireAfterAccess(3600, SECONDS); 310 try { 311 // even to the same value is not allowed 312 builder.expireAfterAccess(3600, SECONDS); 313 fail(); 314 } catch (IllegalStateException expected) { 315 } 316 } 317 318 @GwtIncompatible // java.time.Duration testTimeToIdle_setTwice_duration()319 public void testTimeToIdle_setTwice_duration() { 320 CacheBuilder<Object, Object> builder = 321 CacheBuilder.newBuilder().expireAfterAccess(java.time.Duration.ofSeconds(3600)); 322 assertThrows( 323 IllegalStateException.class, 324 () -> builder.expireAfterAccess(java.time.Duration.ofSeconds(3600))); 325 } 326 327 @SuppressWarnings("ReturnValueIgnored") testTimeToIdleAndToLive()328 public void testTimeToIdleAndToLive() { 329 CacheBuilder.newBuilder() 330 .expireAfterWrite(1, NANOSECONDS) 331 .expireAfterAccess(1, NANOSECONDS) 332 .build(identityLoader()); 333 // well, it didn't blow up. 334 } 335 336 @GwtIncompatible // refreshAfterWrite testRefresh_zero()337 public void testRefresh_zero() { 338 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 339 assertThrows(IllegalArgumentException.class, () -> builder.refreshAfterWrite(0, SECONDS)); 340 } 341 342 @GwtIncompatible // java.time.Duration testRefresh_zero_duration()343 public void testRefresh_zero_duration() { 344 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 345 assertThrows( 346 IllegalArgumentException.class, () -> builder.refreshAfterWrite(java.time.Duration.ZERO)); 347 } 348 349 @GwtIncompatible // refreshAfterWrite testRefresh_setTwice()350 public void testRefresh_setTwice() { 351 CacheBuilder<Object, Object> builder = 352 CacheBuilder.newBuilder().refreshAfterWrite(3600, SECONDS); 353 assertThrows(IllegalStateException.class, () -> builder.refreshAfterWrite(3600, SECONDS)); 354 } 355 356 @GwtIncompatible // java.time.Duration testRefresh_setTwice_duration()357 public void testRefresh_setTwice_duration() { 358 CacheBuilder<Object, Object> builder = 359 CacheBuilder.newBuilder().refreshAfterWrite(java.time.Duration.ofSeconds(3600)); 360 assertThrows( 361 IllegalStateException.class, 362 () -> builder.refreshAfterWrite(java.time.Duration.ofSeconds(3600))); 363 } 364 testTicker_setTwice()365 public void testTicker_setTwice() { 366 Ticker testTicker = Ticker.systemTicker(); 367 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().ticker(testTicker); 368 try { 369 // even to the same instance is not allowed 370 builder.ticker(testTicker); 371 fail(); 372 } catch (IllegalStateException expected) { 373 } 374 } 375 testRemovalListener_setTwice()376 public void testRemovalListener_setTwice() { 377 RemovalListener<Object, Object> testListener = nullRemovalListener(); 378 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().removalListener(testListener); 379 try { 380 // even to the same instance is not allowed 381 builder = builder.removalListener(testListener); 382 fail(); 383 } catch (IllegalStateException expected) { 384 } 385 } 386 testValuesIsNotASet()387 public void testValuesIsNotASet() { 388 assertFalse(CacheBuilder.newBuilder().build().asMap().values() instanceof Set); 389 } 390 391 @GwtIncompatible // CacheTesting testNullCache()392 public void testNullCache() { 393 CountingRemovalListener<Object, Object> listener = countingRemovalListener(); 394 LoadingCache<Object, Object> nullCache = 395 CacheBuilder.newBuilder().maximumSize(0).removalListener(listener).build(identityLoader()); 396 assertEquals(0, nullCache.size()); 397 Object key = new Object(); 398 assertSame(key, nullCache.getUnchecked(key)); 399 assertEquals(1, listener.getCount()); 400 assertEquals(0, nullCache.size()); 401 CacheTesting.checkEmpty(nullCache.asMap()); 402 } 403 404 @GwtIncompatible // QueuingRemovalListener testRemovalNotification_clear()405 public void testRemovalNotification_clear() throws InterruptedException { 406 // If a clear() happens while a computation is pending, we should not get a removal 407 // notification. 408 409 final AtomicBoolean shouldWait = new AtomicBoolean(false); 410 final CountDownLatch computingLatch = new CountDownLatch(1); 411 CacheLoader<String, String> computingFunction = 412 new CacheLoader<String, String>() { 413 @Override 414 public String load(String key) throws InterruptedException { 415 if (shouldWait.get()) { 416 computingLatch.await(); 417 } 418 return key; 419 } 420 }; 421 QueuingRemovalListener<String, String> listener = queuingRemovalListener(); 422 423 final LoadingCache<String, String> cache = 424 CacheBuilder.newBuilder() 425 .concurrencyLevel(1) 426 .removalListener(listener) 427 .build(computingFunction); 428 429 // seed the map, so its segment's count > 0 430 cache.getUnchecked("a"); 431 shouldWait.set(true); 432 433 final CountDownLatch computationStarted = new CountDownLatch(1); 434 final CountDownLatch computationComplete = new CountDownLatch(1); 435 new Thread( 436 new Runnable() { 437 @Override 438 public void run() { 439 computationStarted.countDown(); 440 cache.getUnchecked("b"); 441 computationComplete.countDown(); 442 } 443 }) 444 .start(); 445 446 // wait for the computingEntry to be created 447 computationStarted.await(); 448 cache.invalidateAll(); 449 // let the computation proceed 450 computingLatch.countDown(); 451 // don't check cache.size() until we know the get("b") call is complete 452 computationComplete.await(); 453 454 // At this point, the listener should be holding the seed value (a -> a), and the map should 455 // contain the computed value (b -> b), since the clear() happened before the computation 456 // completed. 457 assertEquals(1, listener.size()); 458 RemovalNotification<String, String> notification = listener.remove(); 459 assertEquals("a", notification.getKey()); 460 assertEquals("a", notification.getValue()); 461 assertEquals(1, cache.size()); 462 assertEquals("b", cache.getUnchecked("b")); 463 } 464 465 // "Basher tests", where we throw a bunch of stuff at a LoadingCache and check basic invariants. 466 467 /** 468 * This is a less carefully-controlled version of {@link #testRemovalNotification_clear} - this is 469 * a black-box test that tries to create lots of different thread-interleavings, and asserts that 470 * each computation is affected by a call to {@code clear()} (and therefore gets passed to the 471 * removal listener), or else is not affected by the {@code clear()} (and therefore exists in the 472 * cache afterward). 473 */ 474 @GwtIncompatible // QueuingRemovalListener 475 testRemovalNotification_clear_basher()476 public void testRemovalNotification_clear_basher() throws InterruptedException { 477 // If a clear() happens close to the end of computation, one of two things should happen: 478 // - computation ends first: the removal listener is called, and the cache does not contain the 479 // key/value pair 480 // - clear() happens first: the removal listener is not called, and the cache contains the pair 481 AtomicBoolean computationShouldWait = new AtomicBoolean(); 482 CountDownLatch computationLatch = new CountDownLatch(1); 483 QueuingRemovalListener<String, String> listener = queuingRemovalListener(); 484 final LoadingCache<String, String> cache = 485 CacheBuilder.newBuilder() 486 .removalListener(listener) 487 .concurrencyLevel(20) 488 .build(new DelayingIdentityLoader<String>(computationShouldWait, computationLatch)); 489 490 int nThreads = 100; 491 int nTasks = 1000; 492 int nSeededEntries = 100; 493 Set<String> expectedKeys = Sets.newHashSetWithExpectedSize(nTasks + nSeededEntries); 494 // seed the map, so its segments have a count>0; otherwise, clear() won't visit the in-progress 495 // entries 496 for (int i = 0; i < nSeededEntries; i++) { 497 String s = "b" + i; 498 cache.getUnchecked(s); 499 expectedKeys.add(s); 500 } 501 computationShouldWait.set(true); 502 503 final AtomicInteger computedCount = new AtomicInteger(); 504 ExecutorService threadPool = Executors.newFixedThreadPool(nThreads); 505 final CountDownLatch tasksFinished = new CountDownLatch(nTasks); 506 for (int i = 0; i < nTasks; i++) { 507 final String s = "a" + i; 508 @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored 509 Future<?> possiblyIgnoredError = 510 threadPool.submit( 511 new Runnable() { 512 @Override 513 public void run() { 514 cache.getUnchecked(s); 515 computedCount.incrementAndGet(); 516 tasksFinished.countDown(); 517 } 518 }); 519 expectedKeys.add(s); 520 } 521 522 computationLatch.countDown(); 523 // let some computations complete 524 while (computedCount.get() < nThreads) { 525 Thread.yield(); 526 } 527 cache.invalidateAll(); 528 tasksFinished.await(); 529 530 // Check all of the removal notifications we received: they should have had correctly-associated 531 // keys and values. (An earlier bug saw removal notifications for in-progress computations, 532 // which had real keys with null values.) 533 Map<String, String> removalNotifications = Maps.newHashMap(); 534 for (RemovalNotification<String, String> notification : listener) { 535 removalNotifications.put(notification.getKey(), notification.getValue()); 536 assertEquals( 537 "Unexpected key/value pair passed to removalListener", 538 notification.getKey(), 539 notification.getValue()); 540 } 541 542 // All of the seed values should have been visible, so we should have gotten removal 543 // notifications for all of them. 544 for (int i = 0; i < nSeededEntries; i++) { 545 assertEquals("b" + i, removalNotifications.get("b" + i)); 546 } 547 548 // Each of the values added to the map should either still be there, or have seen a removal 549 // notification. 550 assertEquals(expectedKeys, Sets.union(cache.asMap().keySet(), removalNotifications.keySet())); 551 assertTrue(Sets.intersection(cache.asMap().keySet(), removalNotifications.keySet()).isEmpty()); 552 } 553 554 /** 555 * Calls get() repeatedly from many different threads, and tests that all of the removed entries 556 * (removed because of size limits or expiration) trigger appropriate removal notifications. 557 */ 558 @GwtIncompatible // QueuingRemovalListener 559 testRemovalNotification_get_basher()560 public void testRemovalNotification_get_basher() throws InterruptedException { 561 int nTasks = 1000; 562 int nThreads = 100; 563 final int getsPerTask = 1000; 564 final int nUniqueKeys = 10000; 565 final Random random = new Random(); // Randoms.insecureRandom(); 566 567 QueuingRemovalListener<String, String> removalListener = queuingRemovalListener(); 568 final AtomicInteger computeCount = new AtomicInteger(); 569 final AtomicInteger exceptionCount = new AtomicInteger(); 570 final AtomicInteger computeNullCount = new AtomicInteger(); 571 CacheLoader<String, String> countingIdentityLoader = 572 new CacheLoader<String, String>() { 573 @Override 574 public String load(String key) throws InterruptedException { 575 int behavior = random.nextInt(4); 576 if (behavior == 0) { // throw an exception 577 exceptionCount.incrementAndGet(); 578 throw new RuntimeException("fake exception for test"); 579 } else if (behavior == 1) { // return null 580 computeNullCount.incrementAndGet(); 581 return null; 582 } else if (behavior == 2) { // slight delay before returning 583 Thread.sleep(5); 584 computeCount.incrementAndGet(); 585 return key; 586 } else { 587 computeCount.incrementAndGet(); 588 return key; 589 } 590 } 591 }; 592 final LoadingCache<String, String> cache = 593 CacheBuilder.newBuilder() 594 .recordStats() 595 .concurrencyLevel(2) 596 .expireAfterWrite(100, MILLISECONDS) 597 .removalListener(removalListener) 598 .maximumSize(5000) 599 .build(countingIdentityLoader); 600 601 ExecutorService threadPool = Executors.newFixedThreadPool(nThreads); 602 for (int i = 0; i < nTasks; i++) { 603 @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored 604 Future<?> possiblyIgnoredError = 605 threadPool.submit( 606 new Runnable() { 607 @Override 608 public void run() { 609 for (int j = 0; j < getsPerTask; j++) { 610 try { 611 cache.getUnchecked("key" + random.nextInt(nUniqueKeys)); 612 } catch (RuntimeException e) { 613 } 614 } 615 } 616 }); 617 } 618 619 threadPool.shutdown(); 620 threadPool.awaitTermination(300, SECONDS); 621 622 // Since we're not doing any more cache operations, and the cache only expires/evicts when doing 623 // other operations, the cache and the removal queue won't change from this point on. 624 625 // Verify that each received removal notification was valid 626 for (RemovalNotification<String, String> notification : removalListener) { 627 assertEquals("Invalid removal notification", notification.getKey(), notification.getValue()); 628 } 629 630 CacheStats stats = cache.stats(); 631 assertEquals(removalListener.size(), stats.evictionCount()); 632 assertEquals(computeCount.get(), stats.loadSuccessCount()); 633 assertEquals(exceptionCount.get() + computeNullCount.get(), stats.loadExceptionCount()); 634 // each computed value is still in the cache, or was passed to the removal listener 635 assertEquals(computeCount.get(), cache.size() + removalListener.size()); 636 } 637 638 @GwtIncompatible // NullPointerTester testNullParameters()639 public void testNullParameters() throws Exception { 640 NullPointerTester tester = new NullPointerTester(); 641 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 642 tester.testAllPublicInstanceMethods(builder); 643 } 644 645 @GwtIncompatible // CacheTesting testSizingDefaults()646 public void testSizingDefaults() { 647 LoadingCache<?, ?> cache = CacheBuilder.newBuilder().build(identityLoader()); 648 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache); 649 assertThat(map.segments).hasLength(4); // concurrency level 650 assertEquals(4, map.segments[0].table.length()); // capacity / conc level 651 } 652 653 @GwtIncompatible // CountDownLatch 654 static final class DelayingIdentityLoader<T> extends CacheLoader<T, T> { 655 private final AtomicBoolean shouldWait; 656 private final CountDownLatch delayLatch; 657 DelayingIdentityLoader(AtomicBoolean shouldWait, CountDownLatch delayLatch)658 DelayingIdentityLoader(AtomicBoolean shouldWait, CountDownLatch delayLatch) { 659 this.shouldWait = shouldWait; 660 this.delayLatch = delayLatch; 661 } 662 663 @Override load(T key)664 public T load(T key) throws InterruptedException { 665 if (shouldWait.get()) { 666 delayLatch.await(); 667 } 668 return key; 669 } 670 } 671 } 672