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