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