• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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